Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .github/agents/04g-governance.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ Reuse current content; recover missing/changed evidence after compaction or resu
2. Run `apex-recall show <project> --json` to verify project context exists (project name, complexity, decisions)
3. **Read the committed baseline subscription entry when present**. It is
comparison evidence, not live policy authority or a prerequisite for live discovery.
A missing baseline routes to live discovery; never fabricate an entry. Determine the target
A missing baseline routes to live discovery; never fabricate an entry. An `alz-corp-default`
baseline has no entry for the target subscription; follow Phase 0.45 instead. Determine the target
subscription ID from the architecture, load the entire
`.github/data/governance-policy-baseline.json.gz → subscriptions[<sub-id>]`
object into context for baseline selection/comparison. The subscription
Expand Down
Binary file modified .github/data/governance-policy-baseline.json.gz
Binary file not shown.
3 changes: 3 additions & 0 deletions .github/instructions/governance-discovery.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ committed under `.github/data/governance-policy-baseline.json.gz`.
**GATE**: This is a mandatory gate. If Azure connectivity fails or policies
cannot be retrieved during live discovery, STOP and inform the user.
Do NOT generate governance constraints from assumptions.
**Single exception**: the user may explicitly choose the shipped `alz-corp-default` baseline as *assumed*
governance (see `baseline-check.md`). It must be labelled assumed and not tenant-verified in every artifact,
and the Step 3.5 review stays mandatory.
Do NOT accept arbitrary static files as governance input — only the approved
workflow output file (`.github/data/governance-policy-baseline.json.gz`) is valid
for cached baseline mode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ same subscription and for offline / sandboxed environments.
## Procedure

1. Check whether `.github/data/governance-policy-baseline.json.gz` exists.
If `--get baseline_kind` prints `alz-corp-default`, follow
[ALZ Corp default baseline](#alz-corp-default-baseline-assumed-governance) instead of steps 2-5.
2. If it exists, read the target subscription ID from the project's
`02-architecture-assessment.md` or session state.
3. **All** eligibility conditions must be true:
Expand Down Expand Up @@ -61,3 +63,37 @@ same subscription and for offline / sandboxed environments.

6. If the baseline file is missing, eligibility fails, or the user
chooses live discovery, proceed to Phase 0.5 (Cache-First Check).
## ALZ Corp default baseline (assumed governance)

The repository ships an offline-generated baseline (`baseline_kind: alz-corp-default`): the default policy a
subscription inherits under the Azure landing zone Corp management group (`root`, `landing_zones` and `corp`
archetypes, pinned in `alz_library_ref`). It holds one synthetic subscription entry, so no target subscription
ID ever matches. It is **assumed governance**, never discovered policy, and adopters replace it by running the
tenant baseline workflow.

1. Read the file's `baseline_kind` and `alz_library_ref`:

```bash
python3 .github/skills/apex-azure-governance-discovery/scripts/governance_baseline.py \
.github/data/governance-policy-baseline.json.gz --get baseline_kind
```

2. When the target subscription is not a key in `subscriptions`, use `askQuestions` with the options
**Run live discovery** (recommended when Azure access exists) and **Use ALZ Corp default as assumed
governance**. Never choose the default on the user's behalf.
3. If the user picks live discovery, proceed to Phase 0.5.
4. If the user picks the default, extract and render it:

```bash
python3 .github/skills/apex-azure-governance-discovery/scripts/governance_baseline.py \
.github/data/governance-policy-baseline.json.gz --alz-default > /tmp/{project}-baseline-sub.json
```

Then run `render_cached_governance.py` as in step 5 above, adding `--subscription <target-subscription-id>`.
The renderer refuses the default without it and writes the target ID, not the synthetic baseline ID, into the
artifact (the synthetic ID stays in `provenance.baseline_subscription_id`). The preview carries an **Assumed
governance** banner; keep it in `04-governance-constraints.md`.
5. Record the choice with `apex-recall decide <project> --decision "Governance assumed from the ALZ Corp
default baseline, not tenant-verified" --rationale "<why live discovery was not used>" --json`.
6. The Step 3.5 governance-reconciliation review stays mandatory; do not bypass it for assumed governance.
Treat any tag contract as the greenfield fallback, because the ALZ Corp set has no tag policy.
18 changes: 16 additions & 2 deletions .github/skills/apex-azure-governance-discovery/scripts/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,26 @@ def _fetch(url: str, expected_id: str) -> dict[str, Any] | None:
return list(pool.map(_fetch, urls, expected_ids))


# Built-in diagnostic-settings initiatives gate each member's effect on a resource-type list parameter.
_CONDITIONAL_EFFECT = re.compile(
r"\[if\(contains\(parameters\('([^']+)'\),\s*'([^']+)'\),\s*parameters\('([^']+)'\),\s*'([^']+)'\)\]",
re.IGNORECASE,
)


def _resolve_parameters(value: Any, parameters: dict[str, Any]) -> Any:
if isinstance(value, str):
normalized = {key.lower(): item for key, item in parameters.items()}
match = re.fullmatch(r"\[parameters\('([^']+)'\)\]", value.strip(), re.IGNORECASE)
if match:
normalized = {key.lower(): item for key, item in parameters.items()}
return normalized.get(match.group(1).lower(), value)
conditional = _CONDITIONAL_EFFECT.fullmatch(value.strip())
if conditional:
container = normalized.get(conditional.group(1).lower())
chosen = normalized.get(conditional.group(3).lower())
if isinstance(container, list) and chosen is not None:
listed = {str(item).lower() for item in container}
return chosen if conditional.group(2).lower() in listed else conditional.group(4)
Comment on lines +250 to +256

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not porting this here; the PowerShell collector has wider gaps than the conditional expression (member parameters, assignment overrides, PARTIAL reporting) and the shipped baseline comes from the Python path. Tracked in #763 so the collectors can be aligned as one change with a shared fixture test.

if isinstance(value, dict):
return {key: _resolve_parameters(item, parameters) for key, item in value.items()}
if isinstance(value, list):
Expand Down Expand Up @@ -878,7 +892,7 @@ def discover(
audit_count += 1
continue
if eff not in RELEVANT_EFFECTS:
if eff != "Append":
if eff not in {"Append", "enforceSetting"}:
unresolved.append(original_defn.get("id") or pid)
continue

Expand Down
Loading
Loading