This is the development record, published as it was written. It is amended by revision, including where the work went wrong. Nothing here has been rewritten for the web.
Record header
created_at: 2026-09-19T19:57:12-06:00
format: perspicuity-work/1
id: ae-2026-09-19-hosting-and-public-corpus
record_status: open
revision: 1
skill_version: 0.4.0
updated: 2026-09-19
updated_at: 2026-09-19T20:33:44-06:00
work_status: active
Host the check under Perspicuity and publish the corpus with it
Current position
Parent: RECORD.md, revision 7.
Principal: David. Decider: David for hosting this under Perspicuity, for publishing the decision corpus, and for the release word. Rook for the host name and the reversible mechanics inside those choices, under the delegation in RECORD.md. Work owner: Rook. Decision: selected — the tool is hosted as a subproject of Perspicuity AI at **agents.perspicuity.ai**, on the existing server, and the decision corpus is published as a generated page of the public site. David selected the hosting, the corpus publication and the host name on 2026-09-19; Rook recommended the name and supplied the comparison. selected_at 2026-09-19T19:55:00-06:00. Work scope: the published address, the server-side arrangement it implies, and the corpus page as a product feature on the public site. It does not own the Perspicuity project's own records, its DNS zone beyond the single record this needs, or its live Caddy configuration until a deployment plan is submitted. Work: registered at 19:57 with the frame, the alternatives and the selection, before the corpus page was implemented or any server change proposed. The corpus page landed at a079fba; the deployment plan is written and not executed. Reconnaissance against the live host was read-only and is recorded below. Outcome: unknown. Nothing is hosted, no DNS record exists and agents.perspicuity.ai does not currently resolve. The corpus page is built: 13 pages generated from the records, checked by make ci for freshness and for JavaScript, with 16 tests. It is not reachable by anyone until the site is hosted. Next: David — read the deployment plan, add the DNS record for agents.perspicuity.ai, and give the release word. Rook — nothing on the host until that word; the corpus page and the plan are done. Dependency: the DNS record for agents.perspicuity.ai (David) gates go-live, and the record belongs in Cloudflare, not GoDaddy: the domain is registered and its mail runs at GoDaddy, but the zone's nameservers are sullivan and summer.ns.cloudflare.com, so a record added at the registrar would sit in a zone nobody resolves. scripts/check_dns.py reports whether it is in place yet. It does not gate the corpus page, which is local work.
| Stage | began_at | registered_at / exact basis revision | finished_at |
|---|---|---|---|
| Frame and Decide | 2026-09-19T19:45:00-06:00 | 2026-09-19T19:57:12-06:00 / this revision | 2026-09-19T19:57:12-06:00 |
| Act | 2026-09-19T19:57:12-06:00 | 2026-09-19T19:57:12-06:00 / this revision | pending |
| Review | pending | pending | pending |
Frame and Decide
David's instruction is the frame, and its shape is unusual: he asked for a host name shortlist *and* said he did not want to buy a domain, then asked whether this belongs under Perspicuity because the project exists to showcase the method. So the real question is not "which domain" but:
Where does this live so that the corpus is the reason to trust the tool, rather than a separate artefact nobody reads?
Two of his own observations decide most of it. The corpus is intended for publication, and the tool's credibility rests on showing the checks. Hosting the two at the same address lets a sceptical supplier reach the record behind a check in one step. That is a product argument, not a branding one.
The second observation is the honest counter-argument, which he raised himself: Perspicuity is not front-facing enough for a supplier who has never heard of it. A third condition arrived with his instruction to use GoDaddy: the domain is bought and its mail runs there, but its DNS zone is at Cloudflare, so the one record this needs is added in a different place from the one he named. That is recorded because the same assumption would misdirect anyone adding a host name to this domain later. The resolution adopted here keeps the two jobs separate — the parent brand supplies credibility, the product does the talking — and is recorded because it sets the precedent for how any later tool under this organisation is presented.
Fundamental objectives
| # | Fundamental objective | Source | Measure, direction and horizon |
|---|---|---|---|
| O1 | A sceptical supplier can check the tool's claims | The project's two required outcomes; CONTEXT.md | Steps from a report to the record behind a check; down; at first use |
| O2 | The tool is credible to someone who has never heard of us | David's question about whether Perspicuity is front-facing enough | A stranger can tell who publishes this and why; at first visit |
| O3 | The product can be marketed separately from the parent | David's question about separate marketing | The product's address says what it does; at any later move to its own domain |
| O4 | The published surface costs nothing extra to run | Standing constraints: standard library, static-first, no new dependency | No new process, service or paid dependency; continuous |
Material conditions
| Material condition | Type | Basis | Affects |
|---|---|---|---|
| No new domain will be bought now | Given | David's instruction of 2026-09-19 | The host must be a subdomain of one already held |
perspicuity.ai resolves on Caddy at 87.99.156.208 and returns HTTP 200 | Given | Read-only check of the live host, 2026-09-19 | The parent can host it; the same server already runs six host blocks, including two unrelated domains |
The perspicuity.ai block sets form-action 'none' and connect-src 'none' | Given | /etc/caddy/Caddyfile on the live host, read 2026-09-19 | A sub-path under that origin would be refused by the browser when the check form submits |
| That block allowlists seven paths and 404s everything else | Given | Same | Every new file under that origin is a live-config edit |
agents.perspicuity.ai does not resolve | Given | curl from the host, 2026-09-19 | The name is free; a DNS record by David is required |
Perspicuity runs its own deployment at /var/www/perspicuity-releases/<release>/public | Given | Live host | The release-directory convention to follow |
| The Perspicuity project is actively worked in by another agent session | Given | Its AGENTS.md names a primary agent; Decisions/ records are being filed | This record must not author its records or edit its configuration |
| Its Caddyfile already carries commented instructions to replace only named blocks | Given | Comments in the live file | The precedent for how a change there is proposed: narrowly, validated, and not by reloading blind |
Alternatives and consequences
| # | Where it lives | O1 corpus reachable | O2 credible stranger | O3 separable | O4 free | Cost and risk |
|---|---|---|---|---|---|---|
| A1 | Sub-path of perspicuity.ai | Yes | Yes | No — the address is the parent's | Yes | Blocked by the live config: the CSP refuses the form, and every path is an allowlist edit on a site another session owns |
| A2 | Subdomain of perspicuity.ai with its own Caddy block | Yes | Yes | Yes — its own origin and its own name; a later move to a bought domain is a redirect | Yes | One DNS record (David), one host block, one release directory. Precedent exists on this host twice over |
| A3 | Subdomain of peopleandplanet.consulting | Yes | Partly — the company brand, not the method | Yes | Yes | Available today, and forecloses the corpus argument: the method is the reason to trust the tool, and the company site does not carry it |
| A4 | New domain purchased | Yes | Yes | Yes | Yes | David ruled it out for now on cost and effort; it also delays the window for no gain that A2 does not already give |
The decisive tradeoff. A2 costs one DNS record and buys both halves of the argument: the tool sits under the brand whose credibility it borrows, and its own origin means its policy, its name and its future are independent. A1 fails on the live configuration rather than on taste — the current content-security policy would refuse the product's only input — and would require loosening the marketing site's policy to admit a tool that needs forms and an API. A3 is the cheapest and throws away the reason for doing this at all.
What would warrant reconsideration. If the product outgrows the parent's audience — for instance if suppliers arrive from a channel where "Perspicuity" reads as irrelevant — the answer is a bought domain with a redirect, which A2 makes cheap. If a second tool appears under the same brand, the naming rule chosen here should be revisited once, for both, rather than twice.
Selection
selected_at 2026-09-19T19:55:00-06:00. A2, at agents.perspicuity.ai, with the decision corpus published as a page of the site.
Three things were decided, and the attribution matters:
- Hosting under Perspicuity AI, and the corpus published with the tool — David. He selected both explicitly, with the reason that the project exists to showcase the method and that showing the corpus generated during the tool's own development is the point.
- **
agents.perspicuity.ai— Rook, recommended and accepted.** Shortlist offered:agents.,agentcheck.,check.,readable.,legible.,machinereadable.,found.,eligibility.,getfound..agents.was chosen because it names the audience rather than the claim, which matters for a product whose central discipline is refusing to promise a finding.visible.andfound.alone were rejected for exactly that reason: they imply the outcome the report will not predict. - The product keeps its own name and voice — Rook. "Agent eligibility check" stays the product name; the parent is the credibility anchor and the host, not the product's front. This is the resolution of David's own question about whether Perspicuity is front-facing enough, and it is recorded because it sets the precedent for later tools under this organisation.
A boundary this record states rather than assumes. The Perspicuity project is another session's live work, with its own records and its own authority. This record does not author its records, does not edit its configuration, and does not treat David's "deploy there as needed" as authority to change a running service without a plan he has seen. The deployment plan in Act is submitted, not executed.
Act
Material failure found while writing the plan
**docs/DEPLOY.md was on the published list.** The host facts the plan needed are the same facts that document already carried — the service account, the release paths, the ports — and the allowlist drawn up earlier in this record had included it without reading it with that question in mind. It is now excluded, with docs/DEPLOY-PERSPICUITY.md, and a test asserts both stay out.
Two things about this are worth keeping. The published set was chosen by reading the file names rather than the files, which is how an allowlist ends up permitting something it would have refused. And the fix exposed a second defect: the build only ever wrote pages, so removing DEPLOY.md from the allowlist left the already-published page live. CI caught it on the first run. The build now prunes what the set no longer contains, so the guard is preventing the problem rather than reporting it.
The corpus page
The published corpus is generated from docs/records/, not written by hand, for the same reason the rubric is generated from the checks: a hand-maintained catalogue drifts, and the drift is invisible.
| # | Result | Inputs / dependencies | Owner / timing | Done when | Actual evidence |
|---|---|---|---|---|---|
| U1 | A generator that renders the corpus to static HTML | docs/records/*.md, RECORD.md; the standard library | Rook, this increment | python3 -m eligibility corpus writes a page listing every record with its id, intention, state, date and a link to the rendered record | Met: eligibility/corpus.py, 13 pages, python3 -m eligibility corpus --out build |
| U2 | Rendered records a reader can actually read | The same sources | Rook, this increment | Each record is published as a readable page; no JavaScript; no link escapes the published set; the record's own front matter is shown rather than hidden | Met: every link resolves to a published page *and* to an anchor that exists — which found four links to a #authority section that was a label rather than a heading. It is a heading now |
| U3 | The corpus page is part of the site build | scripts/build_site.py; make ci | Rook, this increment | make ci builds it, and fails if the page is stale against docs/records/ — the same freshness rule the rubric already has | Met: the freshness check compares every page against the generator, and the no-JavaScript guard now covers all 14 pages rather than the original 2 |
| U4 | The deployment plan for agents.perspicuity.ai | The live Caddyfile and release convention on the host; David's DNS record | Rook writes it; David approves it | A narrow, validated host block and release layout that touches no other host, submitted to David before any change | Written, not executed: docs/DEPLOY-PERSPICUITY.md |
Acceptance criteria, registered before the work.
make ciexits 0 andmake recordsis clean.- The corpus page needs no JavaScript; the existing no-JavaScript guard is extended to cover it rather than run beside it.
- A record published on the site is the record in the repository, byte for byte after rendering — the page is not a summary, and it does not get to improve the wording.
- Publication hygiene holds on the published surface: the guard already required by docs/RECORDS.md is applied to the generated output, so nothing from
var/, no credential and no personal data can reach a published page. - The corpus page states what it is: the development record, amended by revision, including the parts that went wrong. It does not present the records as a success story, and the damaged ones stay visible.
Review
| Criterion | Evidence source | Owner, window or trigger | Finding | Response |
|---|---|---|---|---|
| A stranger can reach the record behind a check | The published report and the published corpus | Rook, at go-live | Pending | If it takes more than a step or two, the report should link the specific record |
| The published corpus carries no private material | The generated output; the publication-hygiene guard | Rook, before publication | Pending | A finding blocks publication; it does not get fixed after |
| The corpus page stays current | make ci's freshness check | Rook, continuous | Pending | The check is the mechanism; a failing build is the finding |
| Hosting under the parent brand does not make the product unreadable to a supplier | The first real audits | David, at the review | Unobservable until a supplier arrives | If it reads as a consultancy's side project, revisit the name, not the host |
| Nothing was published, spent or sent before the release word | Git and the host | Rook, continuous | Met so far: reconnaissance was read-only; no DNS record exists; no host configuration was changed | David's release word remains the gate |
| The published set contains nothing operational | The allowlist, its tests, and the manifest the build writes | Rook, before publication | Met after correction: docs/DEPLOY.md was on the list and is not any more; the corpus names what it does not publish, so the exclusions are reviewable rather than implicit | Every future addition to the allowlist is read, not just named |
Delivery acceptance is separate from benefit. This record's delivery is a page and a plan. It establishes nothing about whether a supplier trusts the tool more for seeing the corpus — that would need a supplier, and there are none yet.
Changes
Revision 1, 2026-09-19T19:57:12-06:00. Created with the frame, the alternatives, the selection and the plan, before the corpus page was implemented and before any server change was proposed. Source: David's instruction of 2026-09-19 — keep the domain, shortlist instead of buying, host it as a subproject of Perspicuity AI, and publish the decision corpus with it — and his selection of agents.perspicuity.ai. Reason: the published address and the decision to publish the corpus both change what this project publishes, which is the admission test's third trigger, and the corpus page is now product scope rather than documentation. Affects: the site build, the deployment plan, the report's ability to link a check to its record, and the precedent for later tools under this organisation. Reconnaissance against the live host was read-only. Nothing is published, spent or sent; no DNS record was created and no server configuration was changed.
Revision 2, 2026-09-19T20:01:35-06:00. Adds the corpus page's evidence, the deployment plan and a correction to the published set. Source: the implementation of U1–U4 in this record. Reason: the work is done and two defects were found by doing it, so the record has to carry both rather than the plan alone. Changes: U1–U4 gain their actual evidence; the deployment plan is linked as written and unexecuted; **docs/DEPLOY.md is removed from the published set** because it names the service account, paths and ports, and was included by reading its file name rather than the file; the build now prunes pages the set no longer contains, which CI found by refusing to accept the stale page; the Review gains the criterion for operational material. Revision 1 is preserved in Git at fa9a21c. No selection or material condition in revision 1 is altered. Nothing is published, spent or sent; no DNS record exists and no host configuration was changed.
Revision 3, 2026-09-19T20:05:21-06:00. Pins down where the DNS record is added, and adds a tool that reports whether it exists. Source: David's instruction to use GoDaddy as registrar and DNS provider, checked against public DNS before anything was written. Reason: his assumption and the zone's authority disagree, and a record added in the wrong place is a silent no-op that would look like a deployment failure. Changes: the deployment plan gains the exact record, the exact dashboard path and the reason it must be DNS only; the zone's observed state is recorded — nameservers, the three direct A records, DKIM and DMARC at GoDaddy, no CAA, no DNSSEC, and agents currently NXDOMAIN; scripts/check_dns.py reports in one command whether the record is in place. No selection or material condition in revisions 1–2 is altered. Nothing is published, spent or sent; the check is read-only.
Revision 4, 2026-09-19T20:07:57-06:00. Settles the DNS route and corrects a claim this record made in revision 2. Source: David's choice of Cloudflare over delegating a subdomain to GoDaddy, and a check of Cloudflare's own documentation before writing the instructions. Reason: the previous revision told a reader that turning the proxy on would need Total TLS because Universal SSL does not cover new subdomains. That is true for deeper names such as dev.www.example.com, and false for agents.perspicuity.ai, which is a first-level subdomain and is covered. Being wrong in the safe direction is still being wrong, and it would have sent the next reader to buy something they do not need. Changes: the claim is corrected with the source; the proxy is now rejected for the reasons that actually apply — the origin's own certificate path is simpler, the zone's SSL mode and Total TLS would become load-bearing, and ELIGIBILITY_TRUST_PROXY reads a header that is forgeable unless Cloudflare is genuinely the only path in; the GoDaddy delegation route is recorded as considered and not taken, so nobody takes it by halves; the exact dashboard path and a confirmation step are written out. Also observed and now recorded as given: ports 80 and 443 are open in the host firewall and Caddy holds an issued Let's Encrypt certificate for every existing host name, including auth.perspicuity.ai, and port 8095 is free. No selection or material condition in revisions 1–3 is altered. Nothing is published, spent or sent; the DNS record does not exist yet.
Revision 5, 2026-09-19T20:16:42-06:00. Records that the DNS record was added, that it answers SERVFAIL rather than resolving, and the diagnosis. Source: David's report that he added the records in Cloudflare, and a check of the live zone from two independent resolvers. Reason: scripts/check_dns.py reported "no A record yet" for a name that exists and is broken, which is the wrong instruction — it would have had him add a second record on top of the broken one. Changes: the check now reads the DNS response code and separates NXDOMAIN ("you have not added it") from SERVFAIL ("you added something it cannot serve"), with the action each implies; the deployment plan gains the same distinction and rules out the two explanations that do not apply, DNSSEC and the nameservers. Observed: perspicuity.ai, www. and auth. all answer 87.99.156.208; agents answers SERVFAIL for every record type and NXDOMAIN for no type; a control name that does not exist answers NXDOMAIN. Also recorded from David: the registrar's DNS page states that the zone is managed elsewhere, which is the registrar confirming this record's normal home rather than an error. No selection or material condition in revisions 1–4 is altered. Nothing is published, spent or sent.
Revision 6, 2026-09-19T20:31:35-06:00. Names the cause of the SERVFAIL and closes the diagnosis. Source: David's screenshot of the zone, checked against live DNS. Reason: revision 5 guessed at the cause and put the wrong explanation first; the zone shows three NS records delegating agents.perspicuity.ai to ns1, ns2 and ns3.secureserver.net, which is the half-done version of the route rejected in revision 4 — the parent hands the question to a child zone that does not exist, so the resolver gets nothing. Changes: the deployment plan lists the three causes in the order they have actually occurred on this domain, with delegation first; scripts/check_dns.py says the same. The fix is to delete the three NS rows and leave one A row for 87.99.156.208. The facts are unchanged and no earlier revision is rewritten: revision 4 rejected the delegation route on its merits, and this is what taking half of it looks like from outside. Nothing is published, spent or sent.
Revision 7, 2026-09-19T20:33:44-06:00. Records the record resolving, and a defect in the check itself that the moment exposed. Source: David's removal of the three NS rows and addition of the A row, verified from two independent resolvers. Reason: the record is right and the zone answers intermittently, which is what a recent change looks like before it settles — but scripts/check_dns.py reported failure on a single lookup, and a check that fails at random teaches people to ignore it. Changes: the check retries and distinguishes "wrong" from "not settled yet", reporting how many attempts succeeded and returning success when an ordinary resolver can reach the name; the deployment plan's diagnosis list is unchanged and still correct. Observed: A 87.99.156.208 from Cloudflare's and Google's resolvers; NS now empty; the local resolver reaches the name; measured convergence while writing this — between 5 and 8 of every 10 direct queries to the zone answered correctly. Nothing is deployed. Nothing is published, spent or sent.
How this record connects
It builds on or points to: RECORD, RECORD § authority, CONTEXT, RECORDS § publication hygiene.