diff --git a/README.md b/README.md index caaf757..150dce8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ Product surface: **Git is the database.** The canonical data lives in this repository: - `data/policies.json` — the curated policy register (only changed by reviewed commits; served through a filtered route) -- `data/developments.json` — the automated radar feed, served through a filtered route +- `data/developments.json` — the automated radar feed, combined at read time + with verified legacy announcements from the editorial chronology - `public/data/meta.json` — public collection health metadata - `data/dta-ai-policy-framework.json` — editorial visualization artifact gated by its related policy - `data/timeline.json`, `agencies.json`, `commonwealth-agencies.json` — @@ -29,7 +30,7 @@ Product surface: The maintainer's server runs the collector daily, from its own checkout, over the official sources that reliably permit machine retrieval. Sources protected by browser challenges are kept in the same source catalogue but reviewed through the manual coverage ledger. Candidate pages from browser-only sources are retrieved through a self-hosted Firecrawl instance, falling back to headless Chromium when Firecrawl is unavailable. New items are classified by keyword heuristic by default, or by Claude, an Anthropic model, in batches when the collector's Claude classifier is enabled (it is enabled in production). Either classifier path caps stored confidence at 0.65, so an automated discovery never reads as more certain than an editor's review. Change detections on already-tracked records are different: they store a relevance score of 1 because the score there records certainty that a known instrument's page changed, not classifier confidence — those detections are still editor-gated before anything publishes. Detections are validated and committed. The site reads that data from disk and revalidates hourly; there is no runtime database. -High-confidence detections are staged in `data/source-reviews.json`; a reviewer uses the local stage → approve → publish workflow before they enter the register. Public register and timeline reads only expose verified records. The collector never writes to `policies.json` directly, and CI enforces that. +High-confidence detections are staged in `data/source-reviews.json`; a reviewer uses the local stage → approve → publish workflow before they enter the register. The public policy timeline exposes only verified lifecycle events linked to visible register records. Verified announcements and milestones belong in Developments; legacy examples still stored in `data/timeline.json` are projected there without duplicating their evidence. The collector never writes to `policies.json` directly, and CI enforces that. ## Stack diff --git a/docs/architecture.md b/docs/architecture.md index 676483e..4243f43 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -53,9 +53,9 @@ The boundaries are deliberately strict: | Data | Owner | Publication rule | |---|---|---| | `data/policies.json` | Editorial register | Explicitly approved; public route applies verification and update-review filters | -| `data/developments.json` | Automated radar + editorial annotations | Public route hides dismissed items and leads linked to terminal rejected reviews | +| `data/developments.json` | Automated radar + editorial annotations | Public route hides dismissed items and leads linked to terminal rejected reviews; verified legacy announcements are projected from the editorial chronology | | `data/dta-ai-policy-framework.json` | Editorial visualization artifact | Public page/route requires its related policy to remain publicly verified | -| `data/timeline.json` | Editorial chronology | May await review; public route emits only verified records | +| `data/timeline.json` | Editorial chronology | May await review; the public policy timeline emits only verified register-linked lifecycle events | | `data/agencies.json`, `data/commonwealth-agencies.json` | Editorial directory | Public route removes unverified claims and narrative | | `public/data/meta.json` | Collector/editorial operations | Records coverage and health, not just timestamps | | `data/source-reviews.json` | Review workflow | Drafts only; never displayed as canonical facts | @@ -136,6 +136,10 @@ The boundaries are deliberately strict: a newer one. - Public data-service reads also withhold unverified policies, unverified timeline events, dismissed developments, and unverified agency narrative. + Policy timeline reads are further limited to introduced, amended, repealed, + or superseded events linked to a visible register policy. Verified legacy + announcements and milestones are projected into Developments and deduplicated + against existing development records. Development `relatedPolicyId` values and agency policy associations are projected only when their target policy is also public. Supersession references are projected only when the successor policy is diff --git a/docs/trust-model.md b/docs/trust-model.md index af243a6..af1f6f7 100644 --- a/docs/trust-model.md +++ b/docs/trust-model.md @@ -32,7 +32,9 @@ leaves the official-source allow-list. A timeout, bot challenge, or access denial is recorded as a retrieval failure for manual follow-up rather than treated as proof that the official source no longer exists. Public register and timeline reads withhold non-verified records until that -state is resolved. +state is resolved. The public policy timeline also requires a visible register +link and a policy lifecycle event type. Verified announcements and milestones +surface in Developments instead. For direct documents monitored by the collector, a changed content fingerprint creates an update review linked to the existing policy. While that review is diff --git a/src/app/agencies/page.tsx b/src/app/agencies/page.tsx index c444b1a..1f8e95f 100644 --- a/src/app/agencies/page.tsx +++ b/src/app/agencies/page.tsx @@ -1,16 +1,5 @@ -import type { Metadata } from 'next'; -import { AgenciesBrowser } from '@/components/agencies-browser'; -import { getCommonwealthAgencies } from '@/lib/data-service'; +import { notFound } from 'next/navigation'; -export const revalidate = 3600; - -export const metadata: Metadata = { - title: 'Agency AI Transparency Statements - Policai', - description: - 'Browse Australian Government agency AI transparency statements and public disclosures.', -}; - -export default async function AgenciesPage() { - const agencies = await getCommonwealthAgencies(); - return ; +export default function RetiredAgenciesPage() { + notFound(); } diff --git a/src/app/api/timeline/route.test.ts b/src/app/api/timeline/route.test.ts index f5dae03..687f166 100644 --- a/src/app/api/timeline/route.test.ts +++ b/src/app/api/timeline/route.test.ts @@ -24,7 +24,10 @@ describe('/api/timeline', () => { const response = await GET(new Request('https://example.com/api/timeline?jurisdiction=federal')) - expect(getTimelineEvents).toHaveBeenCalledWith({ jurisdiction: 'federal' }) + expect(getTimelineEvents).toHaveBeenCalledWith( + { jurisdiction: 'federal' }, + { scope: 'policy-register' }, + ) await expect(response.json()).resolves.toEqual({ data: events, total: 1, diff --git a/src/app/api/timeline/route.ts b/src/app/api/timeline/route.ts index fa161ef..29bf286 100644 --- a/src/app/api/timeline/route.ts +++ b/src/app/api/timeline/route.ts @@ -5,6 +5,9 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const jurisdiction = searchParams.get('jurisdiction') || undefined; - const events = await getTimelineEvents({ jurisdiction }); + const events = await getTimelineEvents( + { jurisdiction }, + { scope: 'policy-register' }, + ); return NextResponse.json({ data: events, total: events.length, success: true }); } diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index 84b0ead..97f4987 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -1,62 +1,5 @@ import { notFound } from 'next/navigation'; -import Link from 'next/link'; -import { format } from 'date-fns'; -import { MDXRemote } from 'next-mdx-remote/rsc'; -import { getAllPosts, getPostBySlug } from '@/lib/blog'; -import type { Metadata } from 'next'; -export function generateStaticParams() { - return getAllPosts().map((p) => ({ slug: p.slug })); -} - -export async function generateMetadata({ - params, -}: { - params: Promise<{ slug: string }>; -}): Promise { - const { slug } = await params; - const post = getPostBySlug(slug); - - if (!post) { - return {}; - } - - return { - title: `${post.title} - Policai Blog`, - description: post.description, - }; -} - -export default async function BlogPostPage({ - params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - const post = getPostBySlug(slug); - - if (!post) { - notFound(); - } - - return ( -
- - -
-

{post.title}

-

- {format(new Date(post.date), 'dd MMMM yyyy')} -

-
- -
- -
-
- ); +export default function RetiredBlogPostPage() { + notFound(); } diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index 6ce4051..9e0c6ba 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -1,44 +1,5 @@ -import { getAllPosts } from '@/lib/blog'; -import { format } from 'date-fns'; -import Link from 'next/link'; -import { ArrowRight } from 'lucide-react'; -import { PageIntro } from '@/components/layout'; +import { notFound } from 'next/navigation'; -export const metadata = { - title: 'Blog - Policai', - description: 'Project updates and AI policy commentary from Policai.', -}; - -export default function BlogPage() { - const posts = getAllPosts(); - - return ( -
- - {posts.length === 0 ? ( -

No posts yet.

- ) : ( -
- {posts.map((post) => ( - - - - {post.title} - {post.description} - - - - ))} -
- )} -
- ); +export default function RetiredBlogPage() { + notFound(); } diff --git a/src/app/data/public-data-routes.test.ts b/src/app/data/public-data-routes.test.ts index 3dfef45..bd5abc7 100644 --- a/src/app/data/public-data-routes.test.ts +++ b/src/app/data/public-data-routes.test.ts @@ -97,6 +97,7 @@ describe('/data editorial JSON routes', () => { await expect((await getTimelineJson()).json()).resolves.toEqual(events); expect(getTimelineEvents).toHaveBeenCalledWith(undefined, { includeGenerated: false, + scope: 'policy-register', }); }); }); diff --git a/src/app/data/timeline.json/route.ts b/src/app/data/timeline.json/route.ts index c9e9515..84e3ac9 100644 --- a/src/app/data/timeline.json/route.ts +++ b/src/app/data/timeline.json/route.ts @@ -6,6 +6,9 @@ export const revalidate = 3600; export async function GET() { return NextResponse.json( - await getTimelineEvents(undefined, { includeGenerated: false }), + await getTimelineEvents(undefined, { + includeGenerated: false, + scope: 'policy-register', + }), ); } diff --git a/src/app/framework/page.tsx b/src/app/framework/page.tsx index 6396498..3e0c930 100644 --- a/src/app/framework/page.tsx +++ b/src/app/framework/page.tsx @@ -1,116 +1,5 @@ -import { - PolicyFrameworkMap, - type FrameworkData, -} from '@/components/visualizations/PolicyFrameworkMap'; -import { getPolicyFrameworkArtifact } from '@/lib/data-service'; -import { parseCalendarDateForDisplay } from '@/lib/format-policy-date'; -import Link from 'next/link'; -import { ExternalLink, FileText, Download } from 'lucide-react'; -import { PageIntro } from '@/components/layout'; -import { SourceState } from '@/components/policy-table'; +import { notFound } from 'next/navigation'; -export const revalidate = 3600; - -export const metadata = { - title: 'Policy for the Responsible Use of AI in Government | Policai', - description: 'Interactive visualization of Australia\'s Policy for the Responsible Use of AI in Government', -}; - -export default async function FrameworkPage() { - const artifact = await getPolicyFrameworkArtifact(); - if (!artifact) { - return ( -
- -
-

Framework temporarily unavailable

-

- Policai is withholding this derived visualisation while its - source policy and framework data await fingerprinted editorial - re-verification. This prevents an older interpretation from - being presented as current. -

-

- See the{' '} - - methodology and trust model - {' '} - for how records return to public view. -

-
-
- ); - } - const frameworkData = artifact as unknown as FrameworkData; - - return ( -
- - -
- - - View official source - - - - Register entry - - - - Download PDF - -
- - - -
-
-

Effective

-

- {parseCalendarDateForDisplay( - frameworkData.effectiveDate, - ).toLocaleDateString('en-AU', { - year: 'numeric', - month: 'long', - day: 'numeric', - })} -

-
- {frameworkData.lastUpdated && ( -
-

Page updated

-

- {parseCalendarDateForDisplay( - frameworkData.lastUpdated, - ).toLocaleDateString('en-AU', { - year: 'numeric', - month: 'long', - day: 'numeric', - })} -

-
- )} -
-

Verification

-

- -

-
-
-
- ); +export default function RetiredFrameworkPage() { + notFound(); } diff --git a/src/app/map/page.tsx b/src/app/map/page.tsx index fa04241..5b8f313 100644 --- a/src/app/map/page.tsx +++ b/src/app/map/page.tsx @@ -1,21 +1,5 @@ -import type { Metadata } from 'next'; -import { MapBrowser } from '@/components/map-browser'; -import { getPolicies } from '@/lib/data-service'; +import { notFound } from 'next/navigation'; -export const revalidate = 3600; - -export const metadata: Metadata = { - title: 'Australian AI Policy Map - Policai', - description: - 'Explore verified and review-pending Australian AI policy records by jurisdiction.', -}; - -export default async function MapPage() { - const policies = await getPolicies(); - return ( - <> -

Australian AI policy map

- - - ); +export default function RetiredMapPage() { + notFound(); } diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index f146d9b..6348d6b 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -30,21 +30,6 @@ export default async function sitemap(): Promise { changeFrequency: 'weekly', priority: 0.8, }, - { - url: `${BASE_URL}/agencies`, - changeFrequency: 'weekly', - priority: 0.8, - }, - { - url: `${BASE_URL}/map`, - changeFrequency: 'weekly', - priority: 0.6, - }, - { - url: `${BASE_URL}/framework`, - changeFrequency: 'monthly', - priority: 0.6, - }, { url: `${BASE_URL}/timeline`, changeFrequency: 'weekly', diff --git a/src/app/timeline/page.tsx b/src/app/timeline/page.tsx index f56603d..9e8436f 100644 --- a/src/app/timeline/page.tsx +++ b/src/app/timeline/page.tsx @@ -7,12 +7,12 @@ export const revalidate = 3600; export const metadata: Metadata = { title: 'Australian AI Policy Timeline - Policai', description: - 'A source-backed timeline of Australian artificial intelligence policy, governance, and court developments.', + 'A source-backed timeline of lifecycle events for policies in the Australian AI policy register.', }; export default async function TimelinePage() { const [timelineData, policiesData] = await Promise.all([ - getTimelineEvents(), + getTimelineEvents(undefined, { scope: 'policy-register' }), getPolicies(), ]); diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 0f3be76..bab29d7 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -24,13 +24,9 @@ const navItems = [ ]; const insightItems = [ - { href: '/map', label: 'Map' }, - { href: '/agencies', label: 'Agencies' }, { href: '/timeline', label: 'Timeline' }, { href: '/network', label: 'Network' }, - { href: '/framework', label: 'Framework' }, { href: '/methodology', label: 'Methodology' }, - { href: '/blog', label: 'Blog' }, ]; function formatDataDate(value: string | null): string { diff --git a/src/components/policy-browser.tsx b/src/components/policy-browser.tsx index 87613ba..b50eaa5 100644 --- a/src/components/policy-browser.tsx +++ b/src/components/policy-browser.tsx @@ -320,7 +320,7 @@ export function PolicyBrowser({ {( [ { value: policies.length, label: 'verified policies', href: '#policy-register', delta: null }, - { value: distinctJurisdictions.size, label: 'jurisdictions', href: '/map', delta: null }, + { value: distinctJurisdictions.size, label: 'jurisdictions', href: '#policy-register', delta: null }, { value: developmentCount, label: 'developments', href: '/developments', delta: weeklyDevelopmentCount }, { value: automaticSourceCount, label: 'sources monitored', href: '/methodology', delta: null }, ] as const diff --git a/src/components/timeline-browser.tsx b/src/components/timeline-browser.tsx index 0a1bd05..e91e102 100644 --- a/src/components/timeline-browser.tsx +++ b/src/components/timeline-browser.tsx @@ -16,7 +16,7 @@ import { formatPolicyDate } from '@/lib/format-policy-date'; import { parseCalendarDateForDisplay } from '@/lib/format-policy-date'; import { JURISDICTION_NAMES, - TIMELINE_EVENT_TYPES, + POLICY_LIFECYCLE_TIMELINE_EVENT_TYPES, getPolicyTypeName, type Policy, type TimelineEvent, @@ -113,7 +113,7 @@ export function TimelineBrowser({ className="h-11 w-full appearance-none border border-input bg-background pl-10 pr-3 text-sm" > - {TIMELINE_EVENT_TYPES.map((type) => ( + {POLICY_LIFECYCLE_TIMELINE_EVENT_TYPES.map((type) => ( ))} @@ -144,7 +144,7 @@ export function TimelineBrowser({