feat: D19 bounds the review loop; apply nine rulings; close Q46(a) and Q48–Q53

D19 into §3 and swept to all six destinations Pouya named: both agent briefs
(scope + severity filter), /build Phases 2–4 (non-gating questions, scope, stop
signal, two-round cap), /wrap, and CLAUDE.md (comments record decisions, not
history). Sweep verified by command, not recalled.

Rulings applied:
  Q46(a) PUBLISHABLE — three §4 Offerings rows flipped; gate 1 records
         [Pouya's stated basis], never [verified]. Step 5 unblocked.
  Q48    closed, not site-relevant.
  Q49    one §4 row, "Mediator"; read as declining Q49(b), so worksFor stays out.
  Q50    DEVIATES — ships as name + slogan, not the concatenation. Flagged.
  Q51    OBA sections stay; the regulator/voluntary distinction recorded.
  Q52    docs/reference/deploy-credential-verification.md — 18 read-only AWS
         calls, re-run rather than transcribed, access key ID redacted.
  Q53    memberOf emitted on /about/'s Person node.

Two review rounds. The headline finding was this session's own: the Q53 sweep
was asserted and never run, leaving six in-scope records saying memberOf was
withheld — including §12 R10, which is read aloud every session. Round 2 then
found that round 1's simplification had put memberOf on / as well; the per-page
opt-in is restored, because Pouya's ruling turns on /about/'s visible HTML.

Also fixed: MEMBERSHIP_ORGS had orphaned BOUTIQUE's D16 JSDoc; /'s title now
derives from the constants; §7's deploy row stated and retracted three facts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148NztQskLKKApP5SzAA78e
This commit is contained in:
Pouya Lajevardi
2026-08-28 15:52:52 -04:00
co-authored by Claude Opus 5
parent 77a7b410b2
commit fd5f610982
15 changed files with 1165 additions and 280 deletions
+18 -11
View File
@@ -99,7 +99,7 @@ Revisit at month 1218, once there is relationship history to point to.
## Not a practice area yet: tax-adjacent disputes
**Canadian Tax Foundation membership is verified** (`AGENTS.md` §4, 2026-08-26)
**Canadian Tax Foundation membership is verified** (`AGENTS.md` §4 — read the stamp there; a second copy of a currency stamp goes stale where nobody re-reads it, and this one had)
and it is the one credential none of the six areas above touch. Tax-adjacent
disputes are genuinely ADR territory — valuation and purchase-price disputes on
a share sale, indemnity and earn-out fights that turn on a tax position,
@@ -260,17 +260,24 @@ Six cards, one paragraph each, linking onward. Also the natural home for the
pre-dispute technical advisory** — three, and each has an `AGENTS.md` §4
Offerings row.
> 🚫 **THE STRIP MAY NOT SHIP YET, AND HAVING A ROW IS NOT WHAT UNBLOCKS IT.**
> All three rows read **"GATES 0 AND 2 ANSWERED; GATE 1 STILL HAS NO SOURCE —
> NOT YET PUBLISHABLE"**. Pouya ruled on 2026-08-28 that all three pass; §4's
> gate 1 asks *is the activity gated?*, which the register answers with a
> checkable source and not with a ruling, and **no source exists for any of the
> three**. Tracked as **Q46(a)**, which names this strip as the thing it blocks.
> **THE STRIP MAY SHIP — Q46(a) closed 2026-08-28, on Pouya's second
> ruling.** All three §4 Offerings rows now read **PUBLISHABLE**. Read the ENE
> row before writing the strip: gate 1 (*is the activity gated?*) is closed as
> **`[Pouya's stated basis 2026-08-28]`** and **not** as `[verified]`, because no
> source for any of the three exists in `docs/reference/`. That is a deliberate,
> attributed position of the architect's, not a sourced fact — do not describe it
> on the page or in a comment as settled law.
>
> This paragraph read *"each now has a §4 Offerings row, which is what the strip
> needs before it may ship"* until 2026-08-28 — **false as stated**, and it would
> have had an implementer at step 5 ship three offerings the register marks
> unpublishable. Found by `claims-auditor` on re-audit.
> **Two framing constraints survive the ruling and are not discretionary:**
>
> 1. **ENE is an assessment delivered to BOTH parties**, never advice to one. It
> is the offering nearest §4's NOT-NEGOTIABLE boundary — a neutral assessment
> of the *merits* sits closest to *"providing legal services"*.
> 2. **Pre-dispute advisory carries a conflict caution**, and it is practice
> management rather than a publication gate: advisory work for one
> organisation can conflict against a later appointment in the same matter.
> **No copy may imply the offering is free of that tension** — this strip is
> where the temptation to imply it will arise.
> **`settlement counsel` IS STRUCK FROM THIS STRIP AND MUST NOT BE RESTORED.**
> `AGENTS.md` Q42, 2026-08-27. Pouya struck it as his own error in this document:
+7 -3
View File
@@ -48,8 +48,12 @@ D1.
Every page passes through one `SEO` component. A page without it is not finished.
```
title 5060 chars, unique. Pattern: "<Page> · Pouya Lajevardi"
Home: "Pouya Lajevardi · Mediation & Arbitration · Toronto"
title 5060 chars, unique. Pattern: "<Page> · Pouya Lajevardi".
`/` composes its own from the constants — `SITE.name` +
`SITE.tagline` — rather than a literal, so the masthead and
the title cannot drift. This spec carried the literal with an
ampersand after the composition shipped with interpuncts;
cite the constants, do not restate them (AGENTS.md §7 rule).
ARTICLES ARE THE EXCEPTION: no " · Pouya Lajevardi" suffix.
The suffix is 18 chars, so a headline that already reads
5060 renders at 6878 — over this ceiling. Measured against
@@ -101,7 +105,7 @@ JSON-LD only. Validate against Google's Rich Results Test before cutover.
| Type | Where | Notes |
|---|---|---|
| `Person` | `/about/`, referenced site-wide | **Emitted:** `name`, `url`, `jobTitle`, `description`, `alumniOf` (Bond University), `knowsLanguage` (en, fa), `hasCredential` (Q.Med), `sameAs` (LinkedIn), `email`, `image`. **Withheld:** `worksFor`, `memberOf`. *(This enumeration listed `worksFor` as emitted while the same cell said it was withheld, and omitted `url` and `email`, which are — wrong in both directions. The enumeration is the part an implementer copies. Found by `adversarial-reviewer`.)* **CHANGED 2026-08-28 — Q47.** This row read *"`jobTitle` = 'Director of Firm Operations'; omit `worksFor`"*, which put the boutique title on a node whose `url` is this ADR practice's `/about/` — so a consumer could attach it to this entity. Pouya's ruling reframes the field: `jobTitle` describes **this practice**, not the boutique role, which D16 keeps unnamed. The visible role line is unchanged and still reads "Director of Firm Operations at a Toronto litigation and ADR boutique". **THE VALUE IS `PRACTICE_JOB_TITLE` IN `src/data/site.ts` AND THIS ROW DOES NOT RESTATE IT** — §7's rule, applied to a string with a live revert trigger on it: this row carried the literal text for one pass, and `adversarial-reviewer` noted it would go stale the moment the constant moved. Cite, do not copy. **`worksFor` IS WITHHELD** — set for one pass under Q47, then reverted: `ProfessionalService.provider` is this Person, so `provider → Person → worksFor` asserts the same-entity claim `schema.ts` explicitly declines, and §4 says "alongside the practice" where the ruling says "operates through". **`memberOf` is withheld too**, on volatility grounds, even though Q44 closed. Both are **Q49**. See `src/data/schema.ts` |
| `Person` | `/about/`, referenced site-wide | **Emitted:** `name`, `url`, `jobTitle`, `description`, `alumniOf` (Bond University), `knowsLanguage` (en, fa), `hasCredential` (Q.Med), `sameAs` (LinkedIn), `email`, `image`. **Emitted on `/about/` only:** `memberOf` — the four §4 memberships as `Organization` nodes (Q53, ruled 2026-08-28). `/` shows no memberships, so its Person node omits it: structured data represents the page it sits on. **Withheld:** `worksFor` — Q49(b) declined the row 2026-08-28; `provider → Person → worksFor` would assert a same-entity claim §4 does not row. *(This enumeration listed `worksFor` as emitted while the same cell said it was withheld, and omitted `url` and `email`, which are — wrong in both directions. The enumeration is the part an implementer copies. Found by `adversarial-reviewer`.)* **CHANGED 2026-08-28 — Q47.** This row read *"`jobTitle` = 'Director of Firm Operations'; omit `worksFor`"*, which put the boutique title on a node whose `url` is this ADR practice's `/about/` — so a consumer could attach it to this entity. Pouya's ruling reframes the field: `jobTitle` describes **this practice**, not the boutique role, which D16 keeps unnamed. The visible role line is unchanged and still reads "Director of Firm Operations at a Toronto litigation and ADR boutique". **THE VALUE IS `PRACTICE_JOB_TITLE` IN `src/data/site.ts` AND THIS ROW DOES NOT RESTATE IT** — §7's rule, applied to a string with a live revert trigger on it: this row carried the literal text for one pass, and `adversarial-reviewer` noted it would go stale the moment the constant moved. Cite, do not copy. **`worksFor` IS WITHHELD** — set for one pass under Q47, then reverted: `ProfessionalService.provider` is this Person, so `provider → Person → worksFor` asserts the same-entity claim `schema.ts` explicitly declines, and §4 says "alongside the practice" where the ruling says "operates through". **`memberOf` is emitted** — see the sentence above; Q53 closed 2026-08-28. *(This cell asserted `memberOf` was both emitted and withheld for one pass, which is the defect it already records itself being caught for on `worksFor`, in the opposite direction. The enumeration is the part an implementer copies.)* See `src/data/schema.ts` |
| `ProfessionalService` | Home | `areaServed` Toronto/Ontario, `serviceType` **Mediation / Commercial arbitration / Mediation-arbitration (med-arb)***scoped 2026-08-28 on `claims-auditor`'s finding; this row instructed the unscoped class form "Mediation/Arbitration" that Q39 struck and that `schema.ts` deliberately does not follow. Family arbitration carries prescribed training and has its own NOT OFFERED row, so unscoped "Arbitration" is the struck universal in a field nobody reads. Do not widen these strings without a §4 row to widen them from*`provider` → Person, `priceRange` once `/fees/` is real. **Never `LegalService`** — schema.org defines it as a business providing legal advice and *representation*, which asserts in machine-readable form exactly what D13 bars and §4 Forbidden calls out |
| `Service` | Each practice page | `serviceType`, `provider` → Person, `areaServed` |
| `Article` | Each article | `headline`, `description`, `datePublished`, `dateModified`, `author` → Person, `image` |
+13 -25
View File
@@ -356,34 +356,22 @@ Then invalidate `/*`.
**Do NOT add a currency sentence to the page while you are here** — his
ruling is *"list the memberships; promise nothing about their future
state"*, and the struck sentence stays struck.
**Do NOT add `memberOf` to the Person JSON-LD** unless Pouya has said to.
It is withheld deliberately and on narrower grounds than the visible page:
the reason is recorded in `src/data/schema.ts` and **it is an open
judgement, not a settled one — `AGENTS.md` Q53.** Do not restate the
reasoning here; a third copy is how the first two went stale.
**`memberOf` IS EMITTED on `/about/`** — Q53 answered 2026-08-28 and the
withholding is dropped, so the graph asserts the same four memberships the
page shows. **This item therefore covers both**: re-confirming before
cutover means `src/data/schema.ts` as well as the visible list, and they
must not be allowed to diverge.
**§4 records yearly renewal for the OBA sections and the CTF only** — it
says nothing about ADRIC's or ADRIO's period, and an earlier version of
this line asserted "all renew yearly", which §4 does not support.
- [ ] **`AGENTS.md` Q51 answered before cutover** — does listing the **OBA
sections** on `/about/` carry the licensure implication that excludes the
LSO? §4 excludes the Law Society precisely because *"listing the Law Society
among memberships implies licensure"*, and Forbidden bars *any phrasing that
**implies** entitlement to practise law*. The OBA sections **went live on
`/about/` on 2026-08-28** and Q51 was raised the same day, **after** Pouya's
Q44 ruling authorised the group — so the ruling did not consider it.
`adversarial-reviewer` rates it moderate-low confidence as a defect and
**high confidence that the question is unasked**: `grep -n OBA AGENTS.md`
returns rows on renewal, scope and stamping and nothing on implication, and
OBA membership eligibility is not established anywhere in this repo. It sits
beside **R1** — same subject, same page, and not settleable internally. If
the answer is yes, the fix is the LSO fix: exclude deliberately, and record
that it was excluded rather than omitted.
*(This item did not exist until 2026-08-28. Q48, which governs nothing
currently on the site, had a checklist item while Q51, which governs live
public copy, had none.)*
**Also re-check `AGENTS.md` Q48** before re-stamping: if `Q.Med` retention
turns out to depend on ADRIO membership currency, this item covers the
site's central credential and not just a list
- [ ] **The OBA sections stay listed; the LSO stays out** — a check that nobody
has tidied the two into one list, not an open question. `AGENTS.md` **Q51
answered 2026-08-28**: the Law Society is the **regulator**, so membership
*is* licensure; the OBA is a **voluntary association**, which admits
members it does not license. Structural, and independent of eligibility
details — which is what made the question unanswerable inside this repo
before the ruling. **R1 is still live**: same page, same subject, different
question
- [ ] No `TODO(pouya)` remains in any shipped page
- [ ] No matter counts, rates, dollar figures, or testimonials anywhere
- [ ] Q.Arb described as **commenced August 2026** everywhere it appears — §4's
+8 -10
View File
@@ -133,16 +133,14 @@ offer tribunal-secretary work on the site.
Early neutral evaluation, dispute-system design, and pre-dispute technical
advisory: **$500 / hour**.
> 🚫 **A RATE IS NOT A PUBLICATION LICENCE, AND THESE THREE ARE NOT PUBLISHABLE
> YET.** §4's ENE row says the pricing here *"is a fee-page question, not a
> publication licence"*, and all three Offerings rows read **NOT YET
> PUBLISHABLE** — gate 1 (*is the activity gated?*) has no source. **Q46(a)**
> names `docs/07-fees.md` pricing directly as something it blocks.
>
> So: the rate is recorded and settled; **the line item does not go on `/fees/`
> until Q46(a) closes.** This note did not exist until 2026-08-28, which meant
> the gate lived only in `AGENTS.md` while this file says "Build `/fees/` from
> it". Found by `claims-auditor` on re-audit.
> **PUBLISHABLE — Q46(a) closed 2026-08-28.** All three §4 Offerings rows
> read PUBLISHABLE on Pouya's second ruling, so the line item may go on
> `/fees/`. **Read the §4 ENE row first:** gate 1 is closed as **`[Pouya's
> stated basis 2026-08-28]`**, not `[verified]` — there is still no source in
> `docs/reference/` — and the framing constraints in `docs/01` §`/practice/`
> travel with the offering onto this page. In particular, **ENE is priced as an
> assessment delivered to both parties**, and nothing on `/fees/` may read as a
> rate for advising one of them.
**THREE services, not four. `settlement counsel` is struck and must not be
priced** — `AGENTS.md` Q42, Pouya 2026-08-27, correcting his own entry in
@@ -0,0 +1,559 @@
# Deploy credential — verification output
**What this is.** The tool output behind `AGENTS.md` §7's *Deploy credential —
PROVISIONED* row and the two risk downgrades in `AGENTS.md` §10. It exists because the row
was written on evidence that lived only in a terminal, which is the
**Q24 / Q32 shape** R14 exists for: a claim whose supporting artefact is
unreachable is unverifiable by construction, not merely unverified.
`AGENTS.md` **Q52**, ruled 2026-08-28: *"YES — commit the simulate results, the
inline policy, and the `NoSuchBucketPolicy` response, access key ID redacted."*
## Provenance
| | |
|---|---|
| Subject | IAM user `adr-sml-deploy`, AWS account `327082975128` |
| Retrieved | **2026-08-28**, re-run from the repository root |
| Calling identity | `arn:aws:iam::327082975128:user/pouya` — the broadly-permissioned personal user, **at an interactive keyboard**. `AGENTS.md` §7 and §10: acceptable interactively, **never** as a CI credential |
| Calls | 18 — all **read-only**. No `create`, `put`, `attach`, `delete`, or `update` |
| Redaction | The access key ID is replaced with `AKIA…REDACTED` throughout. Nothing else is redacted; `UserId` (`AIDA…`) is retained because it is the durable principal identifier that appears in CloudTrail and grants nothing. The **secret** access key was never requested and cannot be retrieved from the API at all |
| Method | `aws-cli/2.34.53`. Every command's exit status was read, and **no `2>/dev/null` anywhere** — see the `NoSuchBucketPolicy` section, where the error *is* the result |
**Re-run it.** Every command below is copy-pasteable. Nothing here is
transcribed from a session; this file was generated from the captured output.
---
## 1. The user
```console
$ aws iam get-user --user-name adr-sml-deploy
{
"User": {
"Path": "/",
"UserName": "adr-sml-deploy",
"UserId": "AIDAUYJ5KD6MHAAC4BOKD",
"Arn": "arn:aws:iam::327082975128:user/adr-sml-deploy",
"CreateDate": "2026-08-26T15:45:18+00:00"
}
}
```
Created **2026-08-26T15:45:18+00:00**, path `/`, no tags.
## 2. Nothing attached, no groups, one inline policy
```console
$ aws iam list-attached-user-policies --user-name adr-sml-deploy
{
"AttachedPolicies": []
}
$ aws iam list-groups-for-user --user-name adr-sml-deploy
{
"Groups": []
}
$ aws iam list-user-policies --user-name adr-sml-deploy
{
"PolicyNames": [
"adr-sml-deploy-minimal"
]
}
```
**This is the part policy-reading alone gets wrong.** An empty
`AttachedPolicies` proves nothing on its own — permissions can arrive through a
group, or through a resource-based policy that is invisible from the IAM side.
Both are checked: `Groups` is empty here, and **§6 below** checks the resource
side — §5 is the identity-side simulation and does not reach a bucket policy.
## 3. The inline policy — `adr-sml-deploy-minimal`
```console
$ aws iam get-user-policy --user-name adr-sml-deploy \
--policy-name adr-sml-deploy-minimal
{
"UserName": "adr-sml-deploy",
"PolicyName": "adr-sml-deploy-minimal",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListSiteBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::adr-smlcompany-site"
},
{
"Sid": "WriteSiteObjects",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::adr-smlcompany-site/*"
},
{
"Sid": "InvalidateOneDistribution",
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": "arn:aws:cloudfront::327082975128:distribution/E1OK7G98KNKUTA"
}
]
}
}
```
Four actions, one bucket, one distribution. **Never widen it**`AGENTS.md` §10
records why
in terms: this is the control standing between a shared Gitea instance and an
AWS account that also holds `mlp-clientdb-prod-backups-327082975128`. Treat any
request to widen it as a security decision, not a convenience one.
## 4. The access key — issued, never used
```console
$ aws iam list-access-keys --user-name adr-sml-deploy
{
"AccessKeyMetadata": [
{
"UserName": "adr-sml-deploy",
"AccessKeyId": "AKIA…REDACTED",
"Status": "Active",
"CreateDate": "2026-08-26T15:45:19+00:00"
}
]
}
$ aws iam get-access-key-last-used --access-key-id AKIA…REDACTED
{
"UserName": "adr-sml-deploy",
"AccessKeyLastUsed": {
"ServiceName": "N/A",
"Region": "N/A"
}
}
```
One key, `Active`, created **2026-08-26T15:45:19+00:00**.
⚠️ **A correction to `AGENTS.md` §7's wording, found by re-running the call.**
That row said
*"one active access key, `LastUsed` null"*. The API does not return null — it
returns `AccessKeyLastUsed` with **`ServiceName: "N/A"` and `Region: "N/A"`**,
and there is no `LastUsedDate` key at all. Same conclusion (the key has never
authenticated a request), different field shape, and a reader looking for a
literal `null` would not find one.
**Rotation is due 2026-11-26** — key created 2026-08-26, D3 commits to
quarterly. `AGENTS.md` §12 R17 carries the date; `docs/06-deployment.md` §Key rotation
carries the procedure. Create the second key, update the Gitea secrets, verify a
deploy, **then** delete the first.
## 5. `simulate-principal-policy` — nine checks, verified by execution
The point of this section is that it tests the **negative** half. A policy that
grants four actions is only a control if it can be shown not to grant the rest.
```bash
PRINCIPAL=arn:aws:iam::327082975128:user/adr-sml-deploy
sim() { aws iam simulate-principal-policy \
--policy-source-arn "$PRINCIPAL" --action-names "$1" --resource-arns "$2" ; }
```
| # | Action | Resource | `EvalDecision` |
|---|---|---|---|
| 1 | `s3:PutObject` | `arn:aws:s3:::adr-smlcompany-site/index.html` | **allowed** |
| 2 | `s3:DeleteObject` | `arn:aws:s3:::adr-smlcompany-site/old.html` | **allowed** |
| 3 | `s3:ListBucket` | `arn:aws:s3:::adr-smlcompany-site` | **allowed** |
| 4 | `cloudfront:CreateInvalidation` | `arn:aws:cloudfront::327082975128:distribution/E1OK7G98KNKUTA` | **allowed** |
| 5 | `s3:ListBucket` | `arn:aws:s3:::mlp-clientdb-prod-backups-327082975128` | **implicitDeny** |
| 6 | `s3:GetObject` | `arn:aws:s3:::mlp-clientdb-prod-backups-327082975128/anything` | **implicitDeny** |
| 7 | `s3:DeleteBucket` | `arn:aws:s3:::adr-smlcompany-site` | **implicitDeny** |
| 8 | `s3:PutObject` | `arn:aws:s3:::meshkinilaw.ca/anything` | **implicitDeny** |
| 9 | `iam:CreateUser` | `*` | **implicitDeny** |
Four `allowed`, five `implicitDeny`. `implicitDeny` rather than `explicitDeny`
is the correct and expected shape: the policy contains no `Deny` statement, so
everything outside its three `Allow` statements falls through to IAM's
default-deny. An `explicitDeny` here would mean a *different* policy was also in
play.
Raw output for each check, including the full `MatchedStatements` and
`MissingContextValues`, follows in **§7 of this file**. *(Bare `§n` here means a
section of this document; references to the working file are written
`AGENTS.md` §n.)*
## 6. The resource side — `NoSuchBucketPolicy`, and why the contrast call matters
IAM simulation covers identity-based policy. It does **not** cover a
resource-based grant on the backup bucket, which would be invisible from the
principal's side. So:
```console
$ aws s3api get-bucket-policy --bucket mlp-clientdb-prod-backups-327082975128
# exit status: 254
# stdout: 0 bytes
# stderr:
# aws: [ERROR]: An error occurred (NoSuchBucketPolicy) when calling the GetBucketPolicy operation: The bucket policy does not exist
```
**An error is the result here, and that is only legible because stderr was not
suppressed.** `CLAUDE.md`'s convention, from Pouya's own 2026-08-28 correction:
`2>/dev/null` converts *"it failed"* into *"it found nothing"*, and those are
opposite results. The exit status is **254** and stdout is **empty** — read the
status, not just stdout.
**And an empty result needs a working instrument.** The same command against the
site bucket, to prove the call itself resolves a policy when one exists:
```console
$ aws s3api get-bucket-policy --bucket adr-smlcompany-site
{
"Policy": "{\"Version\":\"2008-10-17\",\"Id\":\"PolicyForCloudFrontPrivateContent\",\"Statement\":[{\"Sid\":\"AllowCloudFrontServicePrincipal\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudfront.amazonaws.com\"},\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::adr-smlcompany-site/*\",\"Condition\":{\"StringEquals\":{\"AWS:SourceArn\":\"arn:aws:cloudfront::327082975128:distribution/E1OK7G98KNKUTA\"}}}]}"
}
```
Exit **0**, a real policy returned — the CloudFront OAC grant, scoped to
distribution `E1OK7G98KNKUTA`. So `NoSuchBucketPolicy` on the backup bucket is a
**genuine absence**, not a command that failed to run. That contrast is the
whole reason this call is in the file: without it, an empty result from a broken
command looks exactly like an empty result from a bucket with no policy.
*(This contrast call was **not** part of the original Q22 verification. It was
added on re-run, and it closes the gap that made the original evidence weaker
than it read.)*
## 7. Raw `simulate-principal-policy` responses
### Check 1
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:PutObject",
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site/index.html",
"EvalDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 6,
"Column": 55
},
"EndPosition": {
"Line": 9,
"Column": 57
}
}
],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site/index.html",
"EvalResourceDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 6,
"Column": 55
},
"EndPosition": {
"Line": 9,
"Column": 57
}
}
]
}
]
}
]
}
```
### Check 2
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:DeleteObject",
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site/old.html",
"EvalDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 6,
"Column": 55
},
"EndPosition": {
"Line": 9,
"Column": 57
}
}
],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site/old.html",
"EvalResourceDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 6,
"Column": 55
},
"EndPosition": {
"Line": 9,
"Column": 57
}
}
]
}
]
}
]
}
```
### Check 3
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:ListBucket",
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site",
"EvalDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 3,
"Column": 17
},
"EndPosition": {
"Line": 6,
"Column": 55
}
}
],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site",
"EvalResourceDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 3,
"Column": 17
},
"EndPosition": {
"Line": 6,
"Column": 55
}
}
]
}
]
}
]
}
```
### Check 4
```json
{
"EvaluationResults": [
{
"EvalActionName": "cloudfront:CreateInvalidation",
"EvalResourceName": "arn:aws:cloudfront::327082975128:distribution/E1OK7G98KNKUTA",
"EvalDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 9,
"Column": 57
},
"EndPosition": {
"Line": 12,
"Column": 83
}
}
],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:cloudfront::327082975128:distribution/E1OK7G98KNKUTA",
"EvalResourceDecision": "allowed",
"MatchedStatements": [
{
"SourcePolicyId": "user_adr-sml-deploy_adr-sml-deploy-minimal",
"SourcePolicyType": "IAM Policy",
"StartPosition": {
"Line": 9,
"Column": 57
},
"EndPosition": {
"Line": 12,
"Column": 83
}
}
]
}
]
}
]
}
```
### Check 5
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:ListBucket",
"EvalResourceName": "arn:aws:s3:::mlp-clientdb-prod-backups-327082975128",
"EvalDecision": "implicitDeny",
"MatchedStatements": [],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::mlp-clientdb-prod-backups-327082975128",
"EvalResourceDecision": "implicitDeny"
}
]
}
]
}
```
### Check 6
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:GetObject",
"EvalResourceName": "arn:aws:s3:::mlp-clientdb-prod-backups-327082975128/anything",
"EvalDecision": "implicitDeny",
"MatchedStatements": [],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::mlp-clientdb-prod-backups-327082975128/anything",
"EvalResourceDecision": "implicitDeny"
}
]
}
]
}
```
### Check 7
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:DeleteBucket",
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site",
"EvalDecision": "implicitDeny",
"MatchedStatements": [],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::adr-smlcompany-site",
"EvalResourceDecision": "implicitDeny"
}
]
}
]
}
```
### Check 8
```json
{
"EvaluationResults": [
{
"EvalActionName": "s3:PutObject",
"EvalResourceName": "arn:aws:s3:::meshkinilaw.ca/anything",
"EvalDecision": "implicitDeny",
"MatchedStatements": [],
"MissingContextValues": [],
"EvalDecisionDetails": {},
"ResourceSpecificResults": [
{
"EvalResourceName": "arn:aws:s3:::meshkinilaw.ca/anything",
"EvalResourceDecision": "implicitDeny"
}
]
}
]
}
```
### Check 9
```json
{
"EvaluationResults": [
{
"EvalActionName": "iam:CreateUser",
"EvalResourceName": "*",
"EvalDecision": "implicitDeny",
"MatchedStatements": [],
"MissingContextValues": []
}
]
}
```
---
## What this does and does not establish
**Establishes.** The user exists with the creation date `AGENTS.md` §7 records; it holds one
inline policy and nothing else; the policy grants four actions on one bucket and
one distribution; the four intended actions are `allowed`; five representative
out-of-scope actions — including two against the client-database backup bucket —
are denied; the backup bucket carries no resource-based policy that could grant
around IAM; and one key exists that has never authenticated a request.
**Does not establish.** That the key works (it has never been used, and testing
it would mean putting a real object in the bucket). That Gitea holds it —
`AGENTS.md` §7 records **`GITEA ACTIONS SECRETS: UNSET`**, and Q23 is open on whether a runner
exists at all. That the policy is sufficient for a deploy in practice: the
`s3:AbortMultipartUpload` omission is covered by asset sizes rather than by a
lifecycle rule, and `docs/06-deployment.md` records that with a revisit trigger.
**Not in scope of this file, and deliberately so.** The secret access key. It
must never reach the repository — D3, and `AGENTS.md` §10 on the jointly administered Gitea
instance, where an instance administrator can generally reach repository secrets
or register a runner that receives them.