Write an org baseline¶
An org baseline is the profile your platform or security team publishes for every app to extend. Its locks and bounds make sure no app removes or loosens a required control. Profiles and the org baseline in the tutorial shows an app's side. This recipe is for the team that writes the baseline.
1. Start from a complete one¶
examples/_org/acme-baseline.yaml is the baseline the repository's examples extend, and its tests run in CI:
# Acme's org baseline: the controls every Acme app gets and no app can switch off.
#
# Apps pick it up with `extends: ["org://acme-baseline@^1"]` and find it through
# HARNESSWEAVER_PROFILE_PATH (or --profile-dir), which points at this directory.
# Platform security owns this file; tightening it is a major version (V3), and
# loosening it (1.1.0 allowed pydantic_ai and Acme's own acme plugins) a minor one.
apiVersion: harnessweaver/v1
kind: Profile
metadata: { name: acme-baseline, version: 1.1.0, owner: platform-security }
# Only plugins the platform team has reviewed (S8): first-party ones, and `acme`,
# the namespace of Acme's own guards and middleware.
plugins:
allow: [openai, anthropic, otel, pydantic_ai, acme]
# Internal tools guard their inputs and outputs; riskier apps go further.
require_level:
default: L1
tiers: { customer_facing: L2, takes_actions: L3 }
observability:
capture_content: false # R9
models:
allowed_aliases: [fast, strong] # the aliases the gateway serves
guardrails:
input:
- { id: base-pii, use: pii.regex, action: redact }
output:
- { id: base-secrets, use: secrets.scan, action: block }
tools:
require_manifest: true # S2
approval_by_risk: { high: required, critical: required }
budgets:
per_run: { max_usd: 2.00, deadline: 5m }
locks:
- plugins.allow
- observability.capture_content
- guardrails.input[base-pii]
- guardrails.output[base-secrets]
- tools.require_manifest
- tools.approval_by_risk
# R10: a value outside these fails loading; nothing is clamped.
bounds:
budgets.per_run.max_usd: { max: 5.00 }
budgets.per_run.deadline: { max: 30m }
tasks.*.budget.max_usd: { max: 2.00 }
2. Decide what to lock¶
Lock what must never change, and leave the rest open. A baseline that locks everything gets forked.
| Usually locked | Why |
|---|---|
plugins.allow |
Apps may only use plugins the org approved (RULES S8) |
observability.capture_content |
No prompts or outputs in telemetry (R9) |
guardrails.input[base-pii], guardrails.output[base-secrets] |
The baseline's own guards, by id, so apps can still add their own |
tools.require_manifest, tools.approval_by_risk |
No tool without a manifest (S2), and high-risk calls need a person |
A lock on a keyed-list item (path[id]) forbids changing that item, including enabled: false, but lets apps add items after it. A wildcard (guardrails.input[*]) also stops apps adding items to that list. If a locked guard is declared where: sidecar, lock sidecar.url too. Locks lists every form.
3. Bound numbers instead of locking them¶
bounds let apps go lower, never higher: budgets.per_run.max_usd: { max: 5.00 }. A value outside a bound fails loading with BoundViolation. It's never clamped (R10). Bound task budgets with a wildcard, tasks.*.budget.max_usd.
4. Require an adoption level¶
require_level picks the level an app must reach by its metadata.risk_tier: { default: L1, tiers: { customer_facing: L2, takes_actions: L3 } }. An app below it fails with LevelTooLow (Adoption levels).
5. Version it¶
Give it metadata.version and version it with SemVer. Loosening is a minor version. A new lock or a tighter bound is a major version (RULES V3), and apps pin a range (org://acme-baseline@^1), so a breaking change never reaches an app unannounced. harnessweaver doctor warns about unpinned profiles.
6. Test it¶
Write an overlay that tries to break each lock and bound, and expect each to fail to load. The example's tests do exactly that:
def _load(tmp_path: Path, body: str) -> EffectiveSpec:
spec = tmp_path / "harness.yaml"
spec.write_text(
'apiVersion: harnessweaver/v1\nkind: Harness\nextends: ["org://acme-baseline@^1"]\n' + body,
encoding="utf-8",
)
return load_spec(spec, profile_dirs=[ORG])
def test_the_pii_guard_cant_be_switched_off(tmp_path: Path) -> None:
with pytest.raises(LockViolation):
_load(
tmp_path,
"metadata: { name: notes }\n"
"guardrails:\n input:\n - { id: base-pii, use: pii.regex, enabled: false }\n",
)
7. Distribute it¶
Put it where every app finds it:
- a directory on
HARNESSWEAVER_PROFILE_PATH(separated likePATH), which apps reference asorg://acme-baseline@^1; .harness/profiles/next to the spec, only when the team that owns the app also owns the baseline: a team can edit files in its own repository, so this is no boundary (Who owns a profile);- inside a plugin package, as
hw.Plugin(profiles={"baseline": "profiles/baseline.yaml"}), which apps reference asprofile://acme.baseline@^1(Plugins).
Then review every app's changes against it with harnessweaver spec diff git:main:harness.yaml harness.yaml in pull requests: it shows changed values, locks, profile layers and the adoption level, and reads git: sides with the profiles committed at that ref.