Add repository grant v2 publish scope and adopt daily security flow
ACTIVITY-WP-0046: grant v2 carries a publish scope (remote, branch, path prefixes inside allowed_paths, fast-forward only) in its canonical grant id; v1 stays local-only with unchanged ids. ACTIVITY-WP-0047: disabled daily-security-flow definition dispatching the the-custodian runner as an ops_run (CUST-WP-0079-T03). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 296847@bnt-lap001 Assistant-Session: 4f997d46-1b9d-4131-9699-0c353a215155
This commit is contained in:
parent
f9f2640f38
commit
9737577f0b
9 changed files with 372 additions and 29 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# Custodian Brief — activity-core
|
||||
|
||||
**Domain:** infotech
|
||||
**Last synced:** 2026-10-11 18:31 UTC
|
||||
**Last synced:** 2026-10-13 18:57 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
|
@ -19,8 +19,9 @@ Progress: 1/3 done | workplan_id: `12798750-3eee-5d81-a411-c3c4c5ce2d2c`
|
|||
|
||||
**Open tasks:**
|
||||
- ! Adopt the declared resource set into railiance01 ArgoCD `af09865d`
|
||||
*(wait: Adopted 2026-09-27 (manual sync); automated sync waits on RPF-WP-0048-T02: soak evidence plus founder confirmation of scoped identity/checks (D2).)*
|
||||
- · Establish standing authority for bounded routine releases `8e2b4ed9`
|
||||
*(wait: Automated sync after RPF-WP-0048-T02 soak; founder confirmation given by D2.)*
|
||||
- ! Establish standing authority for bounded routine releases `8e2b4ed9`
|
||||
*(wait: RPF-IN-0011: return dedicated release principal/custody owner task and native acceptance; D2 scope already accepted)*
|
||||
|
||||
### Owner-return pickup adapter for State Hub STATE-WP-0094-T03
|
||||
Progress: 5/6 done | workplan_id: `e65f59d4-2272-557d-9071-734c88186d19`
|
||||
|
|
@ -45,7 +46,12 @@ Progress: 5/8 done | workplan_id: `e1bfb5a4-e647-592d-883f-fe8728ad2f5f`
|
|||
- ! Agree the platform runner contract `a2d95c80`
|
||||
*(wait: Platform owner must accept the candidate request/receipt contract, execution authority checks, limits and credential scope before runtime wiring.)*
|
||||
- ! Refresh the existing backup consumer after the owner repairs the lane `020d19be`
|
||||
*(wait: KV4 provider repair is delivered. Current exact-primary projection needs repo-manager historical-prefix acceptance (owner request 6bf8ca98-ada1-43ab-83e7-207d4ab195fe); separate approved CNPG hostPath/Forgejo GitOps bundle delivery, ESO/env refresh and native consumer receipt remain. Founder per-change go is required before live promotion.)*
|
||||
*(wait: KV4 provider repair is delivered; repo-manager historical-prefix acceptance is delivered (receipt 39f2c067, 2026-10-06). Separate approved CNPG hostPath/Forgejo GitOps bundle delivery, ESO/env refresh and native consumer receipt remain. Founder per-change go is required before live promotion.)*
|
||||
|
||||
## Inbox Hygiene
|
||||
|
||||
**Stale unread:** 1 message(s) older than 3 day(s) — triage at session start.
|
||||
**Missing thread_id:** 2 unread message(s) lack supersession chains.
|
||||
|
||||
---
|
||||
## MCP Orientation (when available)
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@
|
|||
| task | ACTIVITY-WP-0040-T03 | done | — | workplans/ACTIVITY-WP-0040-frontend-patterns-feedback.md |
|
||||
| task | ACTIVITY-WP-0041-T01 | done | — | workplans/ACTIVITY-WP-0041-gitops-adoption.md |
|
||||
| task | ACTIVITY-WP-0041-T02 | wait | — | workplans/ACTIVITY-WP-0041-gitops-adoption.md |
|
||||
| task | ACTIVITY-WP-0041-T03 | todo | — | workplans/ACTIVITY-WP-0041-gitops-adoption.md |
|
||||
| task | ACTIVITY-WP-0041-T03 | wait | — | workplans/ACTIVITY-WP-0041-gitops-adoption.md |
|
||||
| task | ACTIVITY-WP-0042-T01 | done | — | workplans/ACTIVITY-WP-0042-platform-backup-restore-evidence-scheduling.md |
|
||||
| task | ACTIVITY-WP-0042-T02 | wait | — | workplans/ACTIVITY-WP-0042-platform-backup-restore-evidence-scheduling.md |
|
||||
| task | ACTIVITY-WP-0042-T03 | done | — | workplans/ACTIVITY-WP-0042-platform-backup-restore-evidence-scheduling.md |
|
||||
|
|
|
|||
57
activity-definitions/daily-security-flow.md
Normal file
57
activity-definitions/daily-security-flow.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
id: daily-security-flow
|
||||
name: Daily Security Flow
|
||||
enabled: false
|
||||
owner: activity-core
|
||||
governance: custodian
|
||||
status: proposed
|
||||
trigger:
|
||||
type: cron
|
||||
cron_expression: "30 7 * * *"
|
||||
timezone: Europe/Berlin
|
||||
misfire_policy: skip
|
||||
---
|
||||
|
||||
# Daily Security Flow
|
||||
|
||||
Adopted from the-custodian CUST-WP-0079-T03, tracked in ACTIVITY-WP-0047.
|
||||
Activity Core owns the schedule; the-custodian owns the runner
|
||||
(`scripts/security_flow_daily.py`), its report and its delivery checkpoints.
|
||||
|
||||
Each fire emits one ops_run for a rein on the workstation profile, which runs a
|
||||
fixed command in the-custodian checkout:
|
||||
|
||||
```text
|
||||
python3 scripts/security_flow_daily.py --apply
|
||||
```
|
||||
|
||||
The runner resolves hub decisions already recorded as resolved in repo decision
|
||||
logs (the sanctioned `POST /decisions/{id}/resolve`), writes a deterministic
|
||||
JSON report, and messages coding agents only when READY/UNBLOCKED change and the
|
||||
founder only when ATTENDED is nonempty. Its per-recipient checkpoint and run
|
||||
lock make a repeated or overlapping fire safe.
|
||||
|
||||
- Daily 07:30 Europe/Berlin; misfires are skipped, never replayed.
|
||||
- No repository grant: the run reads repo files and changes nothing in any
|
||||
repository. A rein must not commit or push for this run.
|
||||
- Activity Core does not run the script itself (out of scope: general
|
||||
execution); it dispatches and records the ops_run outcome.
|
||||
|
||||
**Not yet enabled.** Activation (ACTIVITY-WP-0047-T02) flips `enabled: true`
|
||||
once the claiming rein confirms it can run the command against workstation
|
||||
checkouts. Rollback: `enabled: false`.
|
||||
|
||||
```rule
|
||||
id: run-daily-security-flow
|
||||
condition: "True"
|
||||
action:
|
||||
task_template: Run the daily security flow
|
||||
description: 'In the-custodian checkout run exactly: python3 scripts/security_flow_daily.py --apply. Report its exit code and the printed JSON report. Do not edit, commit or push any repository and do not send messages yourself; the script handles delivery.'
|
||||
target_repo: the-custodian
|
||||
priority: medium
|
||||
labels: ["automated", "security-flow"]
|
||||
harness_profile_ref: harness.agent-dev-local@1.1.1
|
||||
execution_refs:
|
||||
correlation_id: CUST-WP-0079-T03
|
||||
assignment_ref: ACTIVITY-WP-0047-T01
|
||||
```
|
||||
|
|
@ -267,6 +267,32 @@ types, unknown fields, and publication are rejected at definition sync and
|
|||
again across the full emission batch. The admitted mapping is copied unchanged
|
||||
to the queue claim/read response and never merged into `execution_refs`.
|
||||
|
||||
Repository-grant v2 (ACTIVITY-WP-0046) is the publish-capable form. It keeps
|
||||
the v1 fields, requires `publish: true`, and adds a mandatory `publish_scope`:
|
||||
|
||||
```yaml
|
||||
repository_grant:
|
||||
version: "2"
|
||||
allowed_paths: [checkpoints/]
|
||||
commit_count: {min: 1, max: 1}
|
||||
publish: true
|
||||
publish_scope:
|
||||
remote: origin # plain remote name, never a URL
|
||||
branch: main # plain branch name, no refs/, no ..
|
||||
path_prefixes: [checkpoints/]
|
||||
fast_forward_only: true # the only accepted value; no force push
|
||||
```
|
||||
|
||||
Every publish path prefix must lie inside `allowed_paths`. A v1 grant with
|
||||
`publish: true` or a `publish_scope`, and a v2 grant without one, are
|
||||
rejected. The grant id is the first 32 hex characters of SHA-256 over the
|
||||
canonical JSON (sorted keys, compact separators) of `allowed_paths` (sorted),
|
||||
`commit_count`, `publish`, `version` and, for v2, `publish_scope` with sorted
|
||||
`path_prefixes`. v1 grant ids are unchanged. The rein echoes the grant id in
|
||||
completion evidence and must refuse any push the grant does not cover;
|
||||
normalized evidence may add `remote`, `branch`, `pushed_head`,
|
||||
`fast_forward_only` and `published`.
|
||||
|
||||
`ACTIVITY_CORE_REQUIRE_HARNESS_PROFILE=true` makes a missing profile ref an
|
||||
error both during file sync and emission. Deterministic report-only
|
||||
instructions are exempt because they create no execution request. The flag
|
||||
|
|
|
|||
|
|
@ -198,6 +198,9 @@ def _normalise_repository_grant_evidence(raw: Any) -> dict[str, Any]:
|
|||
"acceptance_policy_id",
|
||||
"version",
|
||||
"allowed_paths_digest",
|
||||
"remote",
|
||||
"branch",
|
||||
"pushed_head",
|
||||
):
|
||||
value = raw.get(key)
|
||||
if isinstance(value, str):
|
||||
|
|
@ -206,9 +209,10 @@ def _normalise_repository_grant_evidence(raw: Any) -> dict[str, Any]:
|
|||
value = raw.get(key)
|
||||
if isinstance(value, int) and not isinstance(value, bool) and value >= 0:
|
||||
result[key] = min(value, 1_000_000)
|
||||
publish = raw.get("publish")
|
||||
if isinstance(publish, bool):
|
||||
result["publish"] = publish
|
||||
for key in ("publish", "fast_forward_only", "published"):
|
||||
value = raw.get(key)
|
||||
if isinstance(value, bool):
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
"""Typed repository mutation authority carried by Activity Core.
|
||||
|
||||
Version 1 is local-only. Version 2 adds an explicit publish scope (remote,
|
||||
branch, path prefixes, fast-forward only) for reins that push their result.
|
||||
|
||||
The v1 schema mirrors the accepted rein-aharness repository-grant contract,
|
||||
but Activity Core owns admission and unchanged queue carriage at its boundary.
|
||||
It deliberately does not infer grants from prose, labels, profiles, or
|
||||
|
|
@ -10,6 +13,7 @@ from __future__ import annotations
|
|||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import (
|
||||
|
|
@ -42,15 +46,69 @@ class CommitCount(BaseModel):
|
|||
return self
|
||||
|
||||
|
||||
class RepositoryGrant(BaseModel):
|
||||
"""Versioned, local-only repository authority envelope."""
|
||||
_REMOTE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
|
||||
_BRANCH_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$")
|
||||
|
||||
|
||||
class PublishScope(BaseModel):
|
||||
"""Where a version-2 grant may push: one remote branch, fast-forward only."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
version: Literal["1"]
|
||||
remote: str
|
||||
branch: str
|
||||
path_prefixes: list[str] = Field(min_length=1, max_length=20)
|
||||
fast_forward_only: Literal[True]
|
||||
|
||||
@field_validator("remote")
|
||||
@classmethod
|
||||
def _validate_remote(cls, value: str) -> str:
|
||||
if not _REMOTE_RE.fullmatch(value):
|
||||
raise ValueError(f"remote must be a plain remote name: {value!r}")
|
||||
return value
|
||||
|
||||
@field_validator("branch")
|
||||
@classmethod
|
||||
def _validate_branch(cls, value: str) -> str:
|
||||
if (
|
||||
not _BRANCH_RE.fullmatch(value)
|
||||
or value == "HEAD"
|
||||
or value.startswith("refs/")
|
||||
or ".." in value
|
||||
or "//" in value
|
||||
or value.endswith(("/", ".", ".lock"))
|
||||
):
|
||||
raise ValueError(f"branch must be a plain branch name: {value!r}")
|
||||
return value
|
||||
|
||||
@field_validator("path_prefixes")
|
||||
@classmethod
|
||||
def _validate_path_prefixes(cls, value: list[str]) -> list[str]:
|
||||
if len(set(value)) != len(value):
|
||||
raise ValueError("path_prefixes must not contain duplicates")
|
||||
for pattern in value:
|
||||
_validate_path_pattern(pattern)
|
||||
return value
|
||||
|
||||
def canonical(self) -> dict[str, Any]:
|
||||
return {
|
||||
"branch": self.branch,
|
||||
"fast_forward_only": self.fast_forward_only,
|
||||
"path_prefixes": sorted(self.path_prefixes),
|
||||
"remote": self.remote,
|
||||
}
|
||||
|
||||
|
||||
class RepositoryGrant(BaseModel):
|
||||
"""Versioned repository authority envelope (v1 local-only, v2 publish)."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
version: Literal["1", "2"]
|
||||
allowed_paths: list[str] = Field(min_length=1, max_length=100)
|
||||
commit_count: CommitCount
|
||||
publish: StrictBool
|
||||
publish_scope: PublishScope | None = None
|
||||
|
||||
@field_validator("allowed_paths")
|
||||
@classmethod
|
||||
|
|
@ -61,33 +119,44 @@ class RepositoryGrant(BaseModel):
|
|||
_validate_path_pattern(pattern)
|
||||
return value
|
||||
|
||||
@field_validator("publish")
|
||||
@classmethod
|
||||
def _local_only(cls, value: bool) -> bool:
|
||||
if value:
|
||||
@model_validator(mode="after")
|
||||
def _publish_matches_version(self) -> RepositoryGrant:
|
||||
if self.version == "1":
|
||||
if self.publish or self.publish_scope is not None:
|
||||
raise ValueError(
|
||||
"version 1 does not grant publication; publish must be false"
|
||||
)
|
||||
return self
|
||||
if not self.publish or self.publish_scope is None:
|
||||
raise ValueError(
|
||||
"version 1 does not grant publication; publish must be false"
|
||||
"version 2 requires publish: true and a publish_scope"
|
||||
)
|
||||
return value
|
||||
for prefix in self.publish_scope.path_prefixes:
|
||||
if not any(_covers(allowed, prefix) for allowed in self.allowed_paths):
|
||||
raise ValueError(
|
||||
f"publish path prefix {prefix!r} is outside allowed_paths"
|
||||
)
|
||||
return self
|
||||
|
||||
def payload(self) -> dict[str, Any]:
|
||||
"""Return the admitted field without semantic rewriting or inference."""
|
||||
return self.model_dump(mode="python")
|
||||
return self.model_dump(mode="python", exclude_none=True)
|
||||
|
||||
@property
|
||||
def grant_id(self) -> str:
|
||||
"""Match rein-aharness's canonical, path-order-independent grant id."""
|
||||
return _digest(
|
||||
{
|
||||
"allowed_paths": sorted(self.allowed_paths),
|
||||
"commit_count": {
|
||||
"max": self.commit_count.max,
|
||||
"min": self.commit_count.min,
|
||||
},
|
||||
"publish": self.publish,
|
||||
"version": self.version,
|
||||
}
|
||||
)[:32]
|
||||
identity: dict[str, Any] = {
|
||||
"allowed_paths": sorted(self.allowed_paths),
|
||||
"commit_count": {
|
||||
"max": self.commit_count.max,
|
||||
"min": self.commit_count.min,
|
||||
},
|
||||
"publish": self.publish,
|
||||
"version": self.version,
|
||||
}
|
||||
if self.publish_scope is not None:
|
||||
identity["publish_scope"] = self.publish_scope.canonical()
|
||||
return _digest(identity)[:32]
|
||||
|
||||
@property
|
||||
def acceptance_policy_id(self) -> str:
|
||||
|
|
@ -128,6 +197,12 @@ def _validate_path_pattern(pattern: str) -> None:
|
|||
raise ValueError("path patterns cannot grant protected .git metadata")
|
||||
|
||||
|
||||
def _covers(allowed: str, prefix: str) -> bool:
|
||||
allowed_parts = allowed.rstrip("/").split("/")
|
||||
prefix_parts = prefix.rstrip("/").split("/")
|
||||
return prefix_parts[: len(allowed_parts)] == allowed_parts
|
||||
|
||||
|
||||
def _digest(value: dict[str, Any]) -> str:
|
||||
payload = json.dumps(value, sort_keys=True, separators=(",", ":"))
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
|
|
|||
|
|
@ -80,3 +80,54 @@ def test_ops_run_schema_has_separate_grant_and_close_identity_columns() -> None:
|
|||
|
||||
assert columns["repository_grant"].nullable is True
|
||||
assert columns["close_intent_digest"].nullable is True
|
||||
|
||||
|
||||
def _publish_grant(**scope_changes: object) -> dict:
|
||||
scope = {
|
||||
"remote": "origin",
|
||||
"branch": "main",
|
||||
"path_prefixes": ["checkpoints/"],
|
||||
"fast_forward_only": True,
|
||||
}
|
||||
scope.update(scope_changes)
|
||||
return _grant(
|
||||
version="2",
|
||||
allowed_paths=["checkpoints/"],
|
||||
publish=True,
|
||||
publish_scope=scope,
|
||||
)
|
||||
|
||||
|
||||
def test_v2_grant_carries_publish_scope_in_payload_and_identity() -> None:
|
||||
raw = _publish_grant()
|
||||
grant = RepositoryGrant.model_validate(raw)
|
||||
narrower = RepositoryGrant.model_validate(
|
||||
_publish_grant(path_prefixes=["checkpoints/2026/"])
|
||||
)
|
||||
|
||||
assert grant.payload() == raw
|
||||
assert grant.grant_id != narrower.grant_id
|
||||
assert "publish_scope" not in RepositoryGrant.model_validate(_grant()).payload()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
_publish_grant(fast_forward_only=False),
|
||||
_publish_grant(remote="https://example.org/repo.git"),
|
||||
_publish_grant(branch="refs/heads/main"),
|
||||
_publish_grant(branch="feature/../main"),
|
||||
_publish_grant(branch="-force"),
|
||||
_publish_grant(path_prefixes=["docs/"]),
|
||||
_publish_grant(path_prefixes=["checkpoints-other/"]),
|
||||
_publish_grant(path_prefixes=[]),
|
||||
_publish_grant(force=True),
|
||||
{**_publish_grant(), "publish": False},
|
||||
{**_publish_grant(), "version": "1"},
|
||||
_grant(version="2", publish=True),
|
||||
_grant(publish_scope=_publish_grant()["publish_scope"]),
|
||||
],
|
||||
)
|
||||
def test_v2_grant_rejects_unsafe_or_inconsistent_publish_scope(value: dict) -> None:
|
||||
with pytest.raises(RepositoryGrantError):
|
||||
validate_repository_grant(value)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
id: ACTIVITY-WP-0046
|
||||
type: workplan
|
||||
title: "Repository grant v2: publish scope for governed pushes (REINAH-WP-0003-T04)"
|
||||
domain: infotech
|
||||
repo: activity-core
|
||||
status: active
|
||||
owner: claude-activity-core
|
||||
topic_slug: activity-core
|
||||
created: "2026-10-13"
|
||||
updated: "2026-10-13"
|
||||
flavor: implementation
|
||||
related: [REINAH-WP-0003, ACTIVITY-WP-0038, ASSEMBLY-WP-0010]
|
||||
---
|
||||
|
||||
Origin: rein-aharness (message 3eb4fa11, 2026-10-13) needs the repository grant
|
||||
that Activity Core carries on each ops_run to scope publication, so that
|
||||
assembly-checkpoint can leave legacy substring routing before
|
||||
`AGENT_HARNESS_LEGACY_APPROACHES_UNTIL` (2026-12-31). Founder chose option 1a
|
||||
on 2026-10-13: Activity Core adds the scope; no temporary tenant-declared grant.
|
||||
|
||||
Grant v1 stays local-only and its grant ids do not change. v2 adds
|
||||
`publish_scope` (remote, branch, path prefixes inside `allowed_paths`,
|
||||
fast-forward only). Activity Core admits and carries the grant; rein-aharness
|
||||
enforces it and refuses any push without it.
|
||||
|
||||
## Admission and carriage
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0046-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Extend `repository_grant.py` with `PublishScope` and version "2"; include the
|
||||
scope in the canonical grant id; reject inconsistent v1/v2 combinations and
|
||||
prefixes outside `allowed_paths`; accept publish evidence keys in
|
||||
`glas_evidence`. Document the contract in `docs/ops-run-queue.md`. Tests in
|
||||
`tests/test_repository_grant.py`.
|
||||
|
||||
## rein-aharness contract confirmation
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0046-T02
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
rein-aharness confirms it computes the same v2 grant id and enforces the scope
|
||||
(REINAH-WP-0003-T04). Wait: reply sent 2026-10-13.
|
||||
|
||||
## assembly-checkpoint definitions adopt v2
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0046-T03
|
||||
status: wait
|
||||
priority: medium
|
||||
```
|
||||
|
||||
assembly-economics declares a v2 grant on its checkpoint definitions and the
|
||||
production image carrying this change is rolled out before 2026-12-15. Wait:
|
||||
T02, then an image release through the normal GitOps path.
|
||||
62
workplans/ACTIVITY-WP-0047-daily-security-flow-adoption.md
Normal file
62
workplans/ACTIVITY-WP-0047-daily-security-flow-adoption.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
id: ACTIVITY-WP-0047
|
||||
type: workplan
|
||||
title: "Adopt the daily security-flow schedule (CUST-WP-0079-T03)"
|
||||
domain: infotech
|
||||
repo: activity-core
|
||||
status: active
|
||||
owner: claude-activity-core
|
||||
topic_slug: activity-core
|
||||
created: "2026-10-13"
|
||||
updated: "2026-10-13"
|
||||
flavor: implementation
|
||||
related: [CUST-WP-0079]
|
||||
---
|
||||
|
||||
Origin: the-custodian handed over the daily security-flow lane (message
|
||||
117e538e, case `case:cross-owner:CUST-WP-0079-T03:activity-core`). The workstation
|
||||
user systemd bus is unavailable, so no host timer can run it. Founder accepted
|
||||
adoption on 2026-10-13.
|
||||
|
||||
Boundary: Activity Core owns the schedule and the ops_run record. the-custodian
|
||||
owns `scripts/security_flow_daily.py`, its report and delivery checkpoints. A
|
||||
rein runs the fixed command; Activity Core does not execute it (SCOPE: no
|
||||
general execution). The run carries no repository grant.
|
||||
|
||||
## Source definition
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0047-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
`activity-definitions/daily-security-flow.md`: daily 07:30 Europe/Berlin,
|
||||
misfire skip, one unconditional rule emitting an ops_run on
|
||||
`harness.agent-dev-local@1.1.1` with the fixed command and correlation
|
||||
`CUST-WP-0079-T03`. Committed with `enabled: false`.
|
||||
|
||||
## Activation
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0047-T02
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Wait: the claiming rein (rein-aharness, workstation profile) confirms it can
|
||||
run the command against workstation checkouts with hub access. Then flip
|
||||
`enabled: true`, release through the normal image/GitOps path, admin-sync, and
|
||||
run one manual trigger.
|
||||
|
||||
## First unassisted fire
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0047-T03
|
||||
status: wait
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Observe one scheduled fire end to end (ops_run completed, report written, no
|
||||
duplicate deliveries) and tell the-custodian so CUST-WP-0079-T03 can close.
|
||||
Wait: T02.
|
||||
Loading…
Add table
Add a link
Reference in a new issue