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
37 changes: 35 additions & 2 deletions .agents/configs/site-inventory.json.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,51 @@
"last_verified": "2026-01-01T00:00:00Z"
}
},
"dns_accounts": {
"cloudflare-example": {
"provider": "cloudflare",
"name": "Example Account",
"account_id_env": "CLOUDFLARE_EXAMPLE_ACCOUNT_ID",
"credential_env": "CLOUDFLARE_EXAMPLE_API_TOKEN",
"last_verified": "2026-01-01T00:00:00Z"
}
},
"sites": {
"example-wordpress-network": {
"name": "Example WordPress Network",
"canonical_hostname": "example.com",
"aliases": ["www.example.com"],
"platform": "wordpress-multisite",
"environment": "production",
"dns": { "provider": "cloudflare", "zone_ref": "example.com" },
"purpose": "Example client's public website network.",
"ownership": {
"type": "client",
"owner": "Example Client Ltd",
"managed_by": "Example Agency",
"confirmed": false,
"evidence": "Example only: inferred from a project brief, awaiting owner confirmation."
},
"dns": { "provider": "cloudflare", "zone_ref": "example.com", "account_ref": "cloudflare-example" },
"hosting": { "provider": "hostinger", "account_ref": "shared-hosting-account", "deployment_path": "/domains/example.com/public_html" },
"provenance": "manual",
"last_verified": "2026-01-01T00:00:00Z",
"children": { "store": { "hostname": "store.example.com", "aliases": ["store-alias.example.com"], "identity": "store", "deployment_path": "/domains/example.com/public_html" } }
"children": {
"store": {
"hostname": "store.example.com",
"aliases": ["store-alias.example.com"],
"identity": "store",
"purpose": "Example client's online store.",
"ownership": {
"type": "client",
"owner": "Example Client Ltd",
"managed_by": "Example Agency",
"confirmed": false,
"evidence": "Example only: inferred from the store brief, awaiting owner confirmation."
},
"dns": { "provider": "cloudflare", "zone_ref": "example.com", "account_ref": "cloudflare-example" },
"deployment_path": "/domains/example.com/public_html"
}
}
}
}
}
20 changes: 20 additions & 0 deletions .agents/reference/site-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ Children may have an `aliases` array, matched after their hostname. A child inhe
its parent hosting account and path unless it overrides the path. Its `dns` record
overrides the parent DNS record when present; otherwise it inherits parent DNS.

Optional `purpose` describes what the site is for in one line. Optional `ownership`
records `type`, `owner` (person or organisation), `managed_by`, `confirmed`, and
`evidence`. Allowed types are `client`, `former-client`, `business` (the operator's
own business), `personal`, `partner`, `community`, and `unknown`. Agents may infer
ownership from evidence, but must record `confirmed: false`, explain that evidence,
and never present an inference as fact. Set `confirmed: true` only after ownership
has been confirmed. A child's `purpose` and whole `ownership` object override the
parent's when present; absent values inherit from the parent, without merging
individual ownership fields.

Top-level `dns_accounts` holds reusable DNS account metadata. The effective `dns`
record's optional `account_ref` must name a key in that map. Lookup resolves it into
`dns.account` with `name`, `credential_env`, `account_id_env`, and `last_verified`.
A child's own DNS record is selected before resolving the account reference; it
does not merge fields from parent DNS. Store only secret variable names in
`credential_env` and `account_id_env`, never tokens or secret account IDs. These
fields are additive and optional; inventory version remains `1`, and inventories
without them retain their existing lookup output. Validation checks ownership
types, boolean confirmation flags, and DNS account references on sites and children.

The helper is read-only. `discover` reports configured local evidence and gaps but
does not call providers or persist anything; provider discovery must be explicitly
implemented and authorized separately. Missing or stale records are evidence gaps,
Expand Down
24 changes: 22 additions & 2 deletions .agents/scripts/site-context-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,23 @@ require_inventory() {
}

validate_inventory() {
jq -e '(.version | type == "number") and (.sites | type == "object") and ([.sites[] | (.canonical_hostname | type == "string") and (.hosting | type == "object")] | all)' "$INVENTORY_PATH" >/dev/null
jq -e '
def valid_metadata($accounts):
(if has("ownership") then
.ownership | type == "object"
and (if has("type") then .type as $type | ["client", "former-client", "business", "personal", "partner", "community", "unknown"] | index($type) != null else true end)
and (if has("confirmed") then .confirmed | type == "boolean" else true end)
else true end)
and (if (.dns // {} | has("account_ref")) then
.dns.account_ref as $ref | ($ref | type == "string") and ($accounts | has($ref))
else true end);
(.dns_accounts // {}) as $accounts
| (.version | type == "number") and (.sites | type == "object")
and ($accounts | type == "object")
and ([.sites[] | (.canonical_hostname | type == "string") and (.hosting | type == "object")
and valid_metadata($accounts)
and ([.children // {} | .[] | valid_metadata($accounts)] | all)] | all)
' "$INVENTORY_PATH" >/dev/null
return $?
}

Expand All @@ -40,7 +56,11 @@ lookup_record() {
(.sites | selected) as $match
| ([($match.value.children // {} | to_entries[]? | select(.value.hostname == $hostname or (.value.aliases // [] | index($hostname))) | .value)][0] // null) as $child
| (.hosting_accounts[$match.value.hosting.account_ref] // {}) as $account
| {status:"found", matched_hostname:$hostname, site_id:$match.key, name:$match.value.name, canonical_hostname:$match.value.canonical_hostname, platform:$match.value.platform, environment:$match.value.environment, dns:($child.dns // $match.value.dns), hosting:($match.value.hosting + {deployment_path:($child.deployment_path // $match.value.hosting.deployment_path), ssh_host:$account.ssh_host, ssh_port:$account.ssh_port, ssh_user:$account.ssh_user, credential_env:$account.credential_env, last_verified:$account.last_verified}), child:$child, provenance:$match.value.provenance, last_verified:$match.value.last_verified}
| ($child.dns // $match.value.dns) as $dns
| (if $dns.account_ref != null then $dns + {account:(.dns_accounts[$dns.account_ref] | {name, credential_env, account_id_env, last_verified})} else $dns end) as $resolved_dns
| {status:"found", matched_hostname:$hostname, site_id:$match.key, name:$match.value.name, canonical_hostname:$match.value.canonical_hostname, platform:$match.value.platform, environment:$match.value.environment, dns:$resolved_dns, hosting:($match.value.hosting + {deployment_path:($child.deployment_path // $match.value.hosting.deployment_path), ssh_host:$account.ssh_host, ssh_port:$account.ssh_port, ssh_user:$account.ssh_user, credential_env:$account.credential_env, last_verified:$account.last_verified}), child:$child, provenance:$match.value.provenance, last_verified:$match.value.last_verified}
+ (if ($child.purpose // $match.value.purpose) != null then {purpose:($child.purpose // $match.value.purpose)} else {} end)
+ (if ($child.ownership // $match.value.ownership) != null then {ownership:($child.ownership // $match.value.ownership)} else {} end)
' "$INVENTORY_PATH"
return $?
}
Expand Down
Loading