# Knowledge Layer — Schema

This is the part of the system that is meant to change constantly. The core prompt (`01-core/system-prompt.md`) never hard-codes a rate, threshold, deadline, or section number — instead, your website backend retrieves relevant entries from this store and injects them into the model call inside a `<knowledge_context>` block. This file defines the entry schema, the changelog schema, and how injection should be formatted.

## 1. Knowledge entry schema

One JSON object per fact/provision. Store these in whatever your backend uses (Postgres table, vector DB metadata, flat JSON — the schema is what matters, not the storage engine).

```json
{
  "id": "string — stable unique slug, e.g. 'in-gst-rr-standard-goods'",
  "topic": "string — short human label, e.g. 'GST standard rate — goods (general rate)'",
  "category": "STATIC | PERIODIC | FREQUENT | HIGHLY_TIME_SENSITIVE",
  "jurisdiction": {
    "country": "e.g. India",
    "state_or_province": "optional",
    "regulator": "e.g. CBIC / CBDT / MCA / ICAI / SEBI / RBI / IBBI"
  },
  "domain": "e.g. GST | income-tax | companies-act | ind-as | as | sa | fema | sebi",
  "applicability": {
    "entity_types": ["company", "LLP", "firm", "individual", "trust", "..."],
    "conditions": "free text — turnover thresholds, sector, transaction type, etc."
  },
  "current_position": {
    "statement": "the actual rule/rate/deadline/requirement, stated precisely",
    "financial_year_or_period": "e.g. FY 2026-27 / AY 2027-28, or 'w.e.f. 01-04-2026'"
  },
  "source": {
    "type": "statute | rule | notification | circular | order | case_law | standard | regulator_guidance | professional_guidance",
    "issuing_authority": "e.g. CBIC, CBDT, MCA, ICAI, SEBI, RBI, IBBI, judiciary",
    "reference": "exact instrument number/citation as issued",
    "url": "link to primary source if available",
    "authority_tier": "1-9, per the Source Hierarchy in the core prompt (§5)"
  },
  "effective_date": "YYYY-MM-DD",
  "superseded_rule": {
    "prior_position": "what this replaced, if anything",
    "effective_until": "YYYY-MM-DD",
    "source_reference": "citation of the earlier instrument"
  },
  "exceptions": ["list of carve-outs, thresholds, grandfathering, special-entity or special-transaction rules"],
  "transitional_provisions": "free text or null",
  "last_verified_date": "YYYY-MM-DD — when a human or a verified fetch last confirmed this is still current",
  "verification_status": "verified_primary_source | verified_secondary_source | user_supplied_unverified | stale_needs_recheck",
  "verified_by": "name/initials of the professional who confirmed it, or 'auto-fetch'",
  "notes": "free text — practical implementation notes, common pitfalls"
}
```

### Field discipline that matters

- **`category`** drives how the core prompt is allowed to talk about this fact when it's *not* present in the retrieved context (see the Knowledge Currency Discipline table, §4 of the core prompt). Get this classification right — a FREQUENT item wrongly marked STATIC is how stale numbers leak into answers.
- **`effective_date`** and **`superseded_rule.effective_until`** must both exist for anything periodic — a lot of real professional errors are "right rule, wrong year."
- **`verification_status`** must never default to `verified_primary_source` on data entry. It should start `user_supplied_unverified` until a human (or an automated fetch with a real source URL) confirms it.
- **`authority_tier`** lets retrieval and the model both reason about what to do when two entries conflict (see §3).

## 2. Changelog schema

Every update to the knowledge store appends a changelog row — this is what makes `CHECK FOR CHANGES` and audit-trail review possible.

```json
{
  "change_id": "uuid or incrementing id",
  "change_date": "YYYY-MM-DD — when this was entered into the system",
  "entry_id": "the knowledge entry id this affects",
  "source": "what was supplied — notification text, circular, judgment, user-provided document",
  "subject": "one-line description",
  "old_position": "prior statement/value",
  "new_position": "new statement/value",
  "effective_date": "YYYY-MM-DD",
  "affected_areas": ["which domains/topics/entity-types this touches"],
  "implementation_implications": "what practically changes for users of the system — e.g. 'existing computations using the old rate for periods after this date are now wrong'",
  "entered_by": "who ran the update (name, or 'agent via UPDATE KNOWLEDGE command')",
  "confirmed_by_user": true
}
```

## 3. Conflict handling

If retrieval returns two entries for the same topic/jurisdiction/period with different `current_position.statement` values:

1. Prefer the entry with the later `last_verified_date`.
2. If verification dates are close/equal, prefer the higher `authority_tier` (lower number = higher authority).
3. If still unresolved, inject **both** into `<knowledge_context>` and let the model surface the conflict to the user rather than silently picking one (per core prompt §4).

## 4. Injection format

When your backend assembles a request, format matched entries as plain text inside the block, one entry per paragraph, e.g.:

```
<knowledge_context>
[Topic: GST standard rate — goods] [Category: FREQUENT] [Jurisdiction: India / CBIC]
Position: <statement>. Applicable: <financial_year_or_period>.
Source: <source.type> <source.reference> (<source.issuing_authority>), effective <effective_date>.
Supersedes: <superseded_rule.prior_position> (until <superseded_rule.effective_until>).
Exceptions: <exceptions>.
Last verified: <last_verified_date> (<verification_status>).
</knowledge_context>
```

Retrieval should be scoped by: jurisdiction match, domain match, and a semantic/topic match against the user's query (or the client context's entity type / transaction type). Cap injected entries to what's actually relevant — flooding the context with unrelated knowledge entries degrades answer quality.

## 5. Staleness sweep

Recommended automated check (can be a scheduled backend job, independent of the chat agent): for every entry where `category` is `FREQUENT` or `HIGHLY_TIME_SENSITIVE`, if `last_verified_date` is older than a threshold (suggested: 30 days for HIGHLY_TIME_SENSITIVE, 90 days for FREQUENT), mark `verification_status = stale_needs_recheck`. A stale entry should still be injected (better than nothing) but the injection text should say `STALE — verify before relying` so the model surfaces that caveat rather than presenting it as current.
