# [Intended] — acknowledging an osy explain finding

> `osy explain` flags a few postures with ⚠ — a public page whose data the public cannot read, a topic open to signed-out callers, a join rule that never asks who. Some of them are deliberate. `[Intended("<finding>", Justification = "…")]` on the page or topic records that decision with its reason: the report still lists the finding, marked as intended and carrying the reason, and stops counting it as open.

<!-- id: security-intended · area: security · stability: preview · html: https://osysharp.com/reference/security/intended/ -->

## Summary        {#summary}
`osy explain` is the report a reviewer reads to answer *who can reach what*. Besides stating every rule, it flags the
places where a page's or a topic's access looks wrong. When one of those is what you meant, say so where it is
declared, and say why:

```osy syntax
[Intended("public-page-gated-data", Justification = "Visitors see the welcome; members see the notes.")]
[AllowAnonymous, Route("/")]
component Welcome() { … }
```

The finding is still in the report — as **✓ intended**, with your reason beside it — and the count at the top of the
report reads `0 open`.

## Signature      {#signature}
```osy syntax
[Intended("<finding>", Justification = "<why>")]   // on a component (a page) or a topic — one per finding
```

| on | `<finding>` | flagged when |
|---|---|---|
| a page | `public-page-gated-data` | an `[AllowAnonymous]` page reads an entity a signed-out visitor may not read |
| a page | `page-auth-broader-than-data` | a page any signed-in user may open reads an entity gated by a role or policy |
| a page | `reads-ungranted-entity` | a page reads an entity that grants read to nobody |
| a topic | `topic-open-to-anonymous` | an `[AllowAnonymous]` topic: a caller with no account may join and publish |
| a topic | `candidates-ask-nothing-about-who` | its `Candidates` never reads the caller, so it answers the same for everyone |
| a topic | `publishers-ask-nothing-about-who` | its `Publishers` never reads the caller |

## Description    {#description}

### It acknowledges; it does not hide   {#visible}
An acknowledged finding stays in the Markdown and in `osy explain --json`. The Markdown says it as decided:

```text
- **Reads** — Note
  - ✓ intended — 'Welcome' is public ([AllowAnonymous]) but 'Note' is not anonymously readable — the page renders
    and the read returns zero rows (a silent empty page). _Acknowledged (`public-page-gated-data`): Visitors see the
    welcome; members see the notes._
```

and the JSON carries the reason on the finding (`"acknowledged": "Visitors see the welcome; …"`), with the report's
`openFindings` and `acknowledgedFindings` beside it. So a reviewer reads the posture and the decision together, and a
report with zero open findings is one where every flag was looked at — not one where flags were switched off.

### One finding, with a reason   {#one-finding}
The mark names exactly one finding, so a different one that appears on the same page later is not acknowledged in
advance; write one `[Intended]` per finding you mean. The reason is required. An `[Intended]` that names no finding the
page or topic can carry is refused with the list above; one with no `Justification` is refused, because the reason is
what the report prints.

### A mark that no longer matches is reported   {#stale}
If the page stops having the finding — the entity became readable, the page gained a gate — the mark is left
acknowledging nothing. The report flags it as open (`stale-acknowledgement`): delete it, or it will quietly acknowledge
the next finding of that kind.

### What it is not for   {#not-for}
- **A sentence marked `(needs review)`** is the report failing to phrase a rule — the platform's to fix, not yours to
  accept — and no attribute applies to it.
- **An `osy lint` finding** is decided with `[SuppressWarning("<rule>")]` (a MUST-tier one cannot be). An `[Intended]`
  naming a lint rule is refused, saying so.
- It changes nothing about who can do what. Access is still exactly what the rules say.

## Examples       {#examples}
```osy title="a public page and a public topic, both deliberate" test app=security-intended
[Principal] entity User {
  [Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated; }
}

entity Note {
  [MaxLength(200)] string Body = "";
  security { allow read when IsAuthenticated; }
}

[Intended("public-page-gated-data", Justification = "Visitors see the welcome; members see the notes.")]
[AllowAnonymous, Route("/")]
component Welcome() {
  render {
    Stack {
      Text("Welcome.");
      foreach (var n in Note.ToList()) { Text(n.Body); }
    }
  }
}

class StatusLine { public string Text = ""; }

[Intended("topic-open-to-anonymous", Justification = "The service status banner is public by design.")]
[AllowAnonymous]
topic ServiceStatus() {
  Candidates = u => u == null || u.Email != "";
  Carries    = StatusLine;
}
```

`osy explain` on it opens with `> **Findings:** 0 open · 2 acknowledged as intended`.

## See also       {#see-also}
- [Explaining your app's security](https://osysharp.com/reference/local/explaining-your-app/) — the report these findings are in
- [public pages (what a signed-out visitor can see and do)](https://osysharp.com/reference/security/public-reads/) — making an entity readable to visitors, when the page should show it to them
- [topic](https://osysharp.com/reference/realtime/topic/) — `Candidates`, `Publishers` and `[AllowAnonymous]` on a topic
- [Checking your app](https://osysharp.com/reference/local/checking-your-app/) — `osy lint`, and `[SuppressWarning]` for its advice
