feat(runtime): implement single-article consolidation runtime and modularize codebase
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/article-input.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "ArticleInputUnit",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"selected_extractor"
|
||||
],
|
||||
"properties": {
|
||||
"articles": false,
|
||||
"selected_extractor": {
|
||||
"type": "string",
|
||||
"enum": ["trafilatura", "newspaper4k", "readability"]
|
||||
},
|
||||
"crawled_url": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"error_message": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"extraction_status": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"http_status": {
|
||||
"type": ["integer", "null"]
|
||||
},
|
||||
"input_meta": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"url": { "type": ["string", "null"] },
|
||||
"titulo": { "type": ["string", "null"] },
|
||||
"subtitulo": { "type": ["string", "null"] },
|
||||
"quando_publicado": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"page_title": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"trafilatura": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"title": { "type": ["string", "null"] },
|
||||
"author": { "type": ["string", "null"] },
|
||||
"date": { "type": ["string", "null"] },
|
||||
"description": { "type": ["string", "null"] },
|
||||
"text": { "type": ["string", "null"] },
|
||||
"markdown": { "type": ["string", "null"] },
|
||||
"canonical_url": { "type": ["string", "null"] },
|
||||
"image": { "type": ["string", "null"] },
|
||||
"language": { "type": ["string", "null"] },
|
||||
"sitename": { "type": ["string", "null"] },
|
||||
"categories": { "type": "array", "items": { "type": "string" } },
|
||||
"tags": { "type": "array", "items": { "type": "string" } },
|
||||
"raw_json": { "type": ["object", "string", "null"] },
|
||||
"pagetype": { "type": ["string", "null"] },
|
||||
"error": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"newspaper4k": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"title": { "type": ["string", "null"] },
|
||||
"authors": { "type": "array", "items": { "type": "string" } },
|
||||
"publish_date": { "type": ["string", "null"] },
|
||||
"meta_description": { "type": ["string", "null"] },
|
||||
"text": { "type": ["string", "null"] },
|
||||
"article_html": { "type": ["string", "null"] },
|
||||
"canonical_link": { "type": ["string", "null"] },
|
||||
"top_image": { "type": ["string", "null"] },
|
||||
"images": { "type": "array", "items": { "type": "string" } },
|
||||
"meta_data": { "type": "object" },
|
||||
"meta_lang": { "type": ["string", "null"] },
|
||||
"meta_site_name": { "type": ["string", "null"] },
|
||||
"tags": { "type": "array", "items": { "type": "string" } },
|
||||
"keywords": { "type": "array", "items": { "type": "string" } },
|
||||
"error": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"readability": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"title": { "type": ["string", "null"] },
|
||||
"short_title": { "type": ["string", "null"] },
|
||||
"author": { "type": ["string", "null"] },
|
||||
"cleaned_text": { "type": ["string", "null"] },
|
||||
"cleaned_html": { "type": ["string", "null"] },
|
||||
"error": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/candidates-payload.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "CandidatesPayload",
|
||||
"description": "Normalized candidate payload supplied to LLM hygiene prompt (projection of internal CandidateObjects)",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"language",
|
||||
"selected_extractor",
|
||||
"metadata_candidates",
|
||||
"block_candidates",
|
||||
"link_candidates",
|
||||
"image_candidates"
|
||||
],
|
||||
"properties": {
|
||||
"language": {
|
||||
"type": "string"
|
||||
},
|
||||
"selected_extractor": {
|
||||
"type": "string",
|
||||
"enum": ["trafilatura", "newspaper4k", "readability"]
|
||||
},
|
||||
"metadata_candidates": {
|
||||
"type": "object",
|
||||
"required": ["title_candidates", "subtitle_candidates", "author_candidates"],
|
||||
"properties": {
|
||||
"title_candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["candidate_id", "source", "text"],
|
||||
"properties": {
|
||||
"candidate_id": { "type": "string" },
|
||||
"source": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"subtitle_candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["candidate_id", "source", "text"],
|
||||
"properties": {
|
||||
"candidate_id": { "type": "string" },
|
||||
"source": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"author_candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["candidate_id", "source", "text"],
|
||||
"properties": {
|
||||
"candidate_id": { "type": "string" },
|
||||
"source": { "type": "string" },
|
||||
"text": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"block_candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["candidate_id", "type", "order_index", "text", "source_extractor"],
|
||||
"properties": {
|
||||
"candidate_id": { "type": "string" },
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["paragraph", "heading", "list_item", "quote"]
|
||||
},
|
||||
"order_index": { "type": "integer" },
|
||||
"text": { "type": "string" },
|
||||
"source_extractor": {
|
||||
"type": "string",
|
||||
"enum": ["trafilatura", "newspaper4k", "readability"]
|
||||
},
|
||||
"equivalences": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"link_candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["candidate_id", "url", "anchor_text", "parent_block_id"],
|
||||
"properties": {
|
||||
"candidate_id": { "type": "string" },
|
||||
"url": { "type": "string" },
|
||||
"anchor_text": { "type": "string" },
|
||||
"parent_block_id": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"image_candidates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["candidate_id", "url"],
|
||||
"properties": {
|
||||
"candidate_id": { "type": "string" },
|
||||
"url": { "type": "string" },
|
||||
"alt": { "type": ["string", "null"] },
|
||||
"caption": { "type": ["string", "null"] },
|
||||
"parent_block_id": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
# CLI Interface Contract: Article Consolidation and Hygiene Runtime
|
||||
|
||||
**Feature Branch**: `006-article-consolidation-runtime`
|
||||
**Date**: 2026-08-23
|
||||
**Contract Version**: `1.0.0`
|
||||
**Status**: Complete
|
||||
|
||||
---
|
||||
|
||||
## 1. Invocation Model
|
||||
|
||||
The runtime executes as a single-article ephemeral Python CLI command. It processes exactly one article input payload and one ECP profile snapshot per invocation, writing output artifacts and state atomically based on paths declared in the versioned configuration, and exiting cleanly with standard exit codes.
|
||||
|
||||
---
|
||||
|
||||
## 2. Command Synopsis
|
||||
|
||||
### 2.1 Main Consolidation Command
|
||||
|
||||
```bash
|
||||
python -m src.cli.consolidate \
|
||||
--input-article <path/to/article.json> \
|
||||
--ecp-snapshot <path/to/ecp_snapshot.json> \
|
||||
--config <path/to/runtime_config.json>
|
||||
```
|
||||
|
||||
### 2.2 Operational Commands
|
||||
|
||||
```bash
|
||||
# Preflight Validation
|
||||
python -m src.cli.preflight --config <path/to/runtime_config.json>
|
||||
|
||||
# Smoke Test
|
||||
python -m src.cli.smoke --config <path/to/runtime_config.json> --fixture <path/to/fixture.json>
|
||||
|
||||
# Reconcile State & Telemetry
|
||||
python -m src.cli.reconcile --config <path/to/runtime_config.json>
|
||||
|
||||
# Resend Pending Telemetry
|
||||
python -m src.cli.telemetry_flush --config <path/to/runtime_config.json>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Options & Arguments
|
||||
|
||||
### `src.cli.consolidate`
|
||||
|
||||
| Option | Flag | Type | Required | Description |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| `--input-article` | `-i` | File Path | Yes | Path to single-article JSON input unit |
|
||||
| `--ecp-snapshot` | `-e` | File Path | Yes | Path to canonical ECP profile snapshot JSON |
|
||||
| `--config` | `-c` | File Path | Yes | Path to approved runtime configuration file |
|
||||
|
||||
### `src.cli.smoke`
|
||||
|
||||
| Option | Flag | Type | Required | Description |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| `--config` | `-c` | File Path | Yes | Path to approved runtime configuration file |
|
||||
| `--fixture` | `-f` | File Path | Yes | Path to smoke test input fixture JSON |
|
||||
|
||||
### `src.cli.preflight` / `reconcile` / `telemetry_flush`
|
||||
|
||||
| Option | Flag | Type | Required | Description |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| `--config` | `-c` | File Path | Yes | Path to approved runtime configuration file |
|
||||
|
||||
*Note on Paths & Options*: All persistence paths (output directory, SQLite database) belong strictly to the versioned configuration file to prevent discrepancies with the deterministic execution fingerprint.
|
||||
|
||||
---
|
||||
|
||||
## 4. Exit Codes
|
||||
|
||||
| Code | Meaning | Description |
|
||||
|:---|:---|:---|
|
||||
| `0` | Success / Handled Rejection | Article successfully processed (`completed_text` or `rejected_ecp`). |
|
||||
| `1` | Input Article / ECP Error | Input article or ECP schema failed local validation (`failed_validation`). |
|
||||
| `2` | Configuration / Preflight Error | Preflight verification failed, config hash mismatch, missing credentials, or invalid config file. |
|
||||
| `3` | Processing Failure | LLM hygiene, enrichment, or gateway fallback failed (`failed_processing`). |
|
||||
| `4` | Persistence Failure | File write, rename, or SQLite lock timeout failed. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Output Protocol
|
||||
|
||||
### 5.1 stdout (Machine-Readable JSON)
|
||||
Every execution emits a single structured JSON object on stdout:
|
||||
|
||||
1. **`consolidate` (Fingerprint Established)**: Emits the complete manifest JSON complying with [`manifest-output.schema.json`](file:///c:/Users/aferr/Projects/AFTech/DunaMedia/TextNLPClassifierApp/specs/006-article-consolidation-runtime/contracts/manifest-output.schema.json).
|
||||
2. **`consolidate` (Article Validation Failure before Fingerprint)**: Emits a structured article failure JSON (Exit Code `1`):
|
||||
```json
|
||||
{
|
||||
"status": "failed_validation",
|
||||
"error_codes": ["INVALID_ARTICLE_SCHEMA"],
|
||||
"message": "Input article payload contains unparseable JSON or schema violation"
|
||||
}
|
||||
```
|
||||
3. **Configuration / Preflight Failure (Exit Code `2`)**: Emits a technical configuration envelope (without inventing article error codes):
|
||||
```json
|
||||
{
|
||||
"status": "configuration_error",
|
||||
"config_error_code": "CONFIG_HASH_MISMATCH",
|
||||
"message": "Runtime configuration SHA-256 does not match certified release-metadata.json"
|
||||
}
|
||||
```
|
||||
4. **Operational Commands**: Emit their specific structured JSON reports (Exit Code `0` on success, `2` on failure):
|
||||
- `preflight`: `{ "status": "ok", "config_version": "1.0.0", "certified_hash_match": true, "checks": [...] }`
|
||||
- `smoke`: `{ "status": "ok", "fingerprint": "...", "duration_ms": 124.5 }`
|
||||
- `reconcile`: `{ "status": "ok", "reconciled_articles": 0, "fixed_states": 0 }`
|
||||
- `telemetry_flush`: `{ "status": "ok", "flushed_events": 5, "remaining_pending": 0 }`
|
||||
|
||||
### 5.2 stderr (Sanitized Technical Logs)
|
||||
Emits structured JSON log lines containing timestamps, log levels, event codes, error details, and trace correlation IDs.
|
||||
- **Structural Sanitization**: Strips HTTP `Authorization`, `Proxy-Authorization`, `X-Api-Key` headers, and token query parameters.
|
||||
- **Exact Token Replacement**: Replaces exact string values of all loaded environment secrets (`GROQ_API_KEY`, `DEEPSEEK_API_KEY`, `LANGFUSE_SECRET_KEY`) with `[REDACTED]`.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/ecp-snapshot.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "ECPSnapshotContract",
|
||||
"description": "Forwarding reference to the external canonical ECP Snapshot schema without local duplicate validation",
|
||||
"$ref": "https://schemas.aftech.internal/ecp/v1/ecp-profile.schema.json"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/enrichment-response.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "ArticleSentimentTagsResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"sentiment",
|
||||
"tags",
|
||||
"evidence_candidate_ids"
|
||||
],
|
||||
"properties": {
|
||||
"sentiment": {
|
||||
"type": "string",
|
||||
"enum": ["positive", "negative", "neutral"]
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 },
|
||||
"minItems": 3,
|
||||
"maxItems": 8,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"evidence_candidate_ids": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/hygiene-response.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "ArticleContentHygieneResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"title_candidate_id",
|
||||
"subtitle_candidate_id",
|
||||
"author_candidate_id",
|
||||
"kept_block_ids",
|
||||
"kept_link_ids",
|
||||
"kept_image_ids",
|
||||
"repairs"
|
||||
],
|
||||
"properties": {
|
||||
"title_candidate_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtitle_candidate_id": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"author_candidate_id": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"kept_block_ids": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"kept_link_ids": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"uniqueItems": true
|
||||
},
|
||||
"kept_image_ids": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"uniqueItems": true
|
||||
},
|
||||
"repairs": {
|
||||
"$ref": "repair-operations.schema.json"
|
||||
},
|
||||
"removal_reasons": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"advertisement",
|
||||
"recommendation",
|
||||
"navigation",
|
||||
"newsletter",
|
||||
"player_interface",
|
||||
"duplicate",
|
||||
"non_editorial"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/manifest-output.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "ArticleConsolidationManifest",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema_version",
|
||||
"fingerprint",
|
||||
"source_url",
|
||||
"selected_extractor",
|
||||
"final_status",
|
||||
"generate_markdown",
|
||||
"markdown_path",
|
||||
"markdown_hash",
|
||||
"ecp_classification",
|
||||
"enrichment",
|
||||
"provider_versions",
|
||||
"model_versions",
|
||||
"prompt_versions",
|
||||
"config_version",
|
||||
"trace_id",
|
||||
"error_codes"
|
||||
],
|
||||
"properties": {
|
||||
"schema_version": {
|
||||
"type": "string",
|
||||
"const": "1.0.0"
|
||||
},
|
||||
"fingerprint": {
|
||||
"type": "string",
|
||||
"minLength": 64,
|
||||
"maxLength": 64
|
||||
},
|
||||
"source_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"selected_extractor": {
|
||||
"type": "string",
|
||||
"enum": ["trafilatura", "newspaper4k", "readability"]
|
||||
},
|
||||
"final_status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"completed_text",
|
||||
"rejected_ecp",
|
||||
"failed_validation",
|
||||
"failed_processing"
|
||||
]
|
||||
},
|
||||
"generate_markdown": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"markdown_path": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"markdown_hash": {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 64,
|
||||
"maxLength": 64
|
||||
},
|
||||
"ecp_classification": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["category", "confidence", "rationale", "evidences"],
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"DIRECT_INHERENT",
|
||||
"CONTEXTUAL_INHERENT",
|
||||
"TANGENTIAL",
|
||||
"NOT_RELATED"
|
||||
]
|
||||
},
|
||||
"confidence": {
|
||||
"type": "number",
|
||||
"minimum": 0.0,
|
||||
"maximum": 1.0
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string"
|
||||
},
|
||||
"evidences": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Grounded textual fragments from intermediate Markdown"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"enrichment": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["sentiment", "tags"],
|
||||
"properties": {
|
||||
"sentiment": {
|
||||
"type": "string",
|
||||
"enum": ["positive", "negative", "neutral"]
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "minLength": 1 },
|
||||
"minItems": 3,
|
||||
"maxItems": 8,
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"provider_versions": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["hygiene", "enrichment"],
|
||||
"properties": {
|
||||
"hygiene": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["provider", "model", "role_config_version"],
|
||||
"properties": {
|
||||
"provider": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"role_config_version": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"enrichment": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["provider", "model", "role_config_version"],
|
||||
"properties": {
|
||||
"provider": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"role_config_version": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"model_versions": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["runtime_primary", "runtime_fallback"],
|
||||
"properties": {
|
||||
"runtime_primary": {
|
||||
"type": "object",
|
||||
"required": ["provider", "model", "role_config_version"],
|
||||
"properties": {
|
||||
"provider": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"role_config_version": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"runtime_fallback": {
|
||||
"type": "object",
|
||||
"required": ["provider", "model", "role_config_version"],
|
||||
"properties": {
|
||||
"provider": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"role_config_version": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"prompt_versions": {
|
||||
"type": ["object", "null"],
|
||||
"required": ["article_content_hygiene", "article_sentiment_tags"],
|
||||
"properties": {
|
||||
"article_content_hygiene": {
|
||||
"type": "object",
|
||||
"required": ["version", "hash"],
|
||||
"properties": {
|
||||
"version": { "type": "string" },
|
||||
"hash": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"article_sentiment_tags": {
|
||||
"type": "object",
|
||||
"required": ["version", "hash"],
|
||||
"properties": {
|
||||
"version": { "type": "string" },
|
||||
"hash": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"config_version": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"trace_id": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"error_codes": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"INVALID_ARTICLE_SCHEMA",
|
||||
"MISSING_SELECTED_EXTRACTOR",
|
||||
"INVALID_SELECTED_EXTRACTOR",
|
||||
"SELECTED_EXTRACTOR_UNAVAILABLE",
|
||||
"MISSING_SOURCE_URL",
|
||||
"MISSING_TITLE_CANDIDATE",
|
||||
"MISSING_CONTENT",
|
||||
"INVALID_ECP_SCHEMA",
|
||||
"HYGIENE_FAILED",
|
||||
"GROUNDING_VIOLATION",
|
||||
"INVALID_TEXT_REPAIR",
|
||||
"ECP_CLASSIFICATION_FAILED",
|
||||
"ECP_REJECTED",
|
||||
"ENRICHMENT_FAILED",
|
||||
"PERSISTENCE_FAILED",
|
||||
"TELEMETRY_PENDING"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": { "final_status": { "const": "completed_text" } }
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"generate_markdown": { "const": true },
|
||||
"markdown_path": { "type": "string", "minLength": 1 },
|
||||
"markdown_hash": { "type": "string", "minLength": 64, "maxLength": 64 },
|
||||
"ecp_classification": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category": { "enum": ["DIRECT_INHERENT", "CONTEXTUAL_INHERENT"] }
|
||||
}
|
||||
},
|
||||
"enrichment": { "type": "object" },
|
||||
"provider_versions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hygiene": { "type": "object" },
|
||||
"enrichment": { "type": "object" }
|
||||
}
|
||||
},
|
||||
"model_versions": { "type": "object" },
|
||||
"prompt_versions": { "type": "object" },
|
||||
"error_codes": {
|
||||
"type": "array",
|
||||
"items": { "const": "TELEMETRY_PENDING" },
|
||||
"maxItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": { "final_status": { "const": "rejected_ecp" } }
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"generate_markdown": { "const": false },
|
||||
"markdown_path": { "type": "null" },
|
||||
"markdown_hash": { "type": "null" },
|
||||
"ecp_classification": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category": { "enum": ["TANGENTIAL", "NOT_RELATED"] }
|
||||
}
|
||||
},
|
||||
"enrichment": { "type": "null" },
|
||||
"provider_versions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hygiene": { "type": "object" },
|
||||
"enrichment": { "type": "null" }
|
||||
}
|
||||
},
|
||||
"model_versions": { "type": "object" },
|
||||
"prompt_versions": { "type": "object" },
|
||||
"error_codes": {
|
||||
"type": "array",
|
||||
"items": { "enum": ["ECP_REJECTED", "TELEMETRY_PENDING"] },
|
||||
"contains": { "const": "ECP_REJECTED" },
|
||||
"minContains": 1,
|
||||
"maxContains": 1,
|
||||
"minItems": 1,
|
||||
"maxItems": 2,
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": { "final_status": { "const": "failed_validation" } }
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"generate_markdown": { "const": false },
|
||||
"markdown_path": { "type": "null" },
|
||||
"markdown_hash": { "type": "null" },
|
||||
"provider_versions": { "type": "null" },
|
||||
"error_codes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": [
|
||||
"INVALID_ARTICLE_SCHEMA",
|
||||
"MISSING_SELECTED_EXTRACTOR",
|
||||
"INVALID_SELECTED_EXTRACTOR",
|
||||
"SELECTED_EXTRACTOR_UNAVAILABLE",
|
||||
"MISSING_SOURCE_URL",
|
||||
"MISSING_TITLE_CANDIDATE",
|
||||
"MISSING_CONTENT",
|
||||
"INVALID_ECP_SCHEMA",
|
||||
"TELEMETRY_PENDING"
|
||||
]
|
||||
},
|
||||
"contains": {
|
||||
"enum": [
|
||||
"INVALID_ARTICLE_SCHEMA",
|
||||
"MISSING_SELECTED_EXTRACTOR",
|
||||
"INVALID_SELECTED_EXTRACTOR",
|
||||
"SELECTED_EXTRACTOR_UNAVAILABLE",
|
||||
"MISSING_SOURCE_URL",
|
||||
"MISSING_TITLE_CANDIDATE",
|
||||
"MISSING_CONTENT",
|
||||
"INVALID_ECP_SCHEMA"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": { "final_status": { "const": "failed_processing" } }
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"generate_markdown": { "const": false },
|
||||
"markdown_path": { "type": "null" },
|
||||
"markdown_hash": { "type": "null" },
|
||||
"error_codes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": [
|
||||
"HYGIENE_FAILED",
|
||||
"GROUNDING_VIOLATION",
|
||||
"INVALID_TEXT_REPAIR",
|
||||
"ECP_CLASSIFICATION_FAILED",
|
||||
"ENRICHMENT_FAILED",
|
||||
"PERSISTENCE_FAILED",
|
||||
"TELEMETRY_PENDING"
|
||||
]
|
||||
},
|
||||
"contains": {
|
||||
"enum": [
|
||||
"HYGIENE_FAILED",
|
||||
"GROUNDING_VIOLATION",
|
||||
"INVALID_TEXT_REPAIR",
|
||||
"ECP_CLASSIFICATION_FAILED",
|
||||
"ENRICHMENT_FAILED",
|
||||
"PERSISTENCE_FAILED"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
# Prompts Versioned Contract: Article Consolidation and Hygiene Runtime
|
||||
|
||||
**Feature Branch**: `006-article-consolidation-runtime`
|
||||
**Date**: 2026-08-23
|
||||
**Contract Version**: `1.0.0`
|
||||
**Status**: Complete
|
||||
|
||||
---
|
||||
|
||||
## 1. Versioned Prompt Registry
|
||||
|
||||
The runtime manages exactly two atomic prompt contracts. Each prompt is versioned independently with an immutable semantic version and content hash.
|
||||
|
||||
| Prompt Identifier | File Path | Semantic Version | Input Context Structure | Expected Output Schema | Promptfoo Suite |
|
||||
|:---|:---|:---|:---|:---|:---|
|
||||
| `article_content_hygiene` | `prompts/article_content_hygiene.v1.txt` | `1.0.0` | 6-Block Context (`candidates-payload.schema.json`) | `hygiene-response.schema.json` | `evals/promptfoo.config.yaml` |
|
||||
| `article_sentiment_tags` | `prompts/article_sentiment_tags.v1.txt` | `1.0.0` | 6-Block Context (Title, subtitle, sanitized Markdown body, language, minimal ECP identity: `qid`, `canonical_name`) | `enrichment-response.schema.json` | `evals/promptfoo.config.yaml` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Normative 6-Block Prompt Ordering (Doc 07 §5.3)
|
||||
|
||||
Every prompt sent to the Model Gateway MUST strictly follow the exact 6-block sequence defined in the normative specification:
|
||||
|
||||
1. **Bloco 1: Regras do Sistema (System Role & Policy Constraints)**
|
||||
Defines the agent/system persona, zero-hallucination mandate, and absolute prohibition of free-form text or Markdown invention.
|
||||
2. **Bloco 2: Responsabilidade da Chamada (Task Responsibility)**
|
||||
Declares the single, narrow responsibility of this specific logical invocation (candidate selection & micro-repair vs sentiment & tag extraction).
|
||||
3. **Bloco 3: Schema e Enums (Output Schema & Enums)**
|
||||
Provides the exact JSON schema and closed allowable categories/enums that the model MUST output.
|
||||
4. **Bloco 4: Contexto Estrutural (Structural Document Context)**
|
||||
Provides structural constraints, backbone extractor definition, metadata slots, and document language.
|
||||
5. **Bloco 5: Candidatos e Evidências (Candidates & Evidence Payloads)**
|
||||
Supplies the candidate blocks, links, images, and untrusted article content clearly delimited and separated from instructions (`<article_candidates>...</article_candidates>`).
|
||||
6. **Bloco 6: Pedido Final de Resposta Estruturada (Final Structured Response Directive)**
|
||||
Final closing directive commanding immediate output of the structured JSON response adhering strictly to the schema.
|
||||
|
||||
---
|
||||
|
||||
## 3. Strict Input Context Specifications
|
||||
|
||||
### 3.1 `article_content_hygiene` (FR-024, FR-058)
|
||||
Receives strictly the normalized candidate payload (`candidates-payload.schema.json`) structured across the 6 blocks above. Article text is treated as untrusted data, enclosed in explicit boundary delimiters.
|
||||
|
||||
### 3.2 `article_sentiment_tags` (FR-037, FR-059)
|
||||
Receives strictly the sanitized editorial context and minimal public ECP identity:
|
||||
- Editorial Document: Final title, subtitle (if present), and intermediate sanitized Markdown body;
|
||||
- Document Language;
|
||||
- Target Entity Identity: strictly `qid` and `canonical_name` (zero ad-hoc keywords, zero description or aliases lists, zero raw ECP snapshot);
|
||||
- Output JSON Schema (`enrichment-response.schema.json`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Contractual Invariants
|
||||
|
||||
1. **Parity Guarantee**: The prompt files deployed in production runtime MUST be byte-for-byte identical (identical SHA-256 hash) to the prompt files evaluated by Promptfoo in `evals/`.
|
||||
2. **Preflight Verification**: During startup and preflight, the runtime recalculates the SHA-256 hash of each prompt file and asserts equality against the approved release metadata (`src/core/release-metadata.json`). Any mismatch aborts execution with exit code `2`.
|
||||
3. **Semantic Version Validation**: Semantic versions in prompt headers are parsed using standard semantic version parsers (`packaging.version` / pure tuple comparison), never regular expressions.
|
||||
4. **Structured Invariants**:
|
||||
- Zero free-form body generation instructions.
|
||||
- Zero historical references or self-healing instructions.
|
||||
- All article text inputs explicitly delimited as untrusted data.
|
||||
5. **Contract Testing**: `tests/contract/test_prompts_contract.py` validates prompt file existence, semver compliance via parser, SHA-256 calculation, schema associations, and byte parity with Promptfoo suites.
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/repair-operations.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "TextRepairOperations",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"target_candidate_id",
|
||||
"original_fragment",
|
||||
"replacement_fragment",
|
||||
"category",
|
||||
"rationale"
|
||||
],
|
||||
"properties": {
|
||||
"target_candidate_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"original_fragment": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"replacement_fragment": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"encoding",
|
||||
"unicode",
|
||||
"spacing",
|
||||
"punctuation_corruption",
|
||||
"obvious_typo"
|
||||
]
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://schemas.aftech.internal/article-consolidation/v1/runtime-config.schema.json",
|
||||
"x-contract-version": "1.0.0",
|
||||
"title": "RuntimeConfigContract",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config_version",
|
||||
"paths",
|
||||
"roles",
|
||||
"prompts",
|
||||
"ecp",
|
||||
"limits",
|
||||
"pricing",
|
||||
"langfuse",
|
||||
"sqlite"
|
||||
],
|
||||
"properties": {
|
||||
"config_version": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "Semantic version of the functional configuration; changes with any model/prompt/parameter adjustment and enters fingerprint"
|
||||
},
|
||||
"paths": {
|
||||
"type": "object",
|
||||
"required": ["output_dir", "sqlite_db"],
|
||||
"properties": {
|
||||
"output_dir": { "type": "string" },
|
||||
"sqlite_db": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"roles": {
|
||||
"type": "object",
|
||||
"required": ["runtime_primary", "runtime_fallback"],
|
||||
"properties": {
|
||||
"runtime_primary": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"role_config_version",
|
||||
"provider",
|
||||
"model",
|
||||
"endpoint_url",
|
||||
"timeout_seconds",
|
||||
"max_retries",
|
||||
"parameters",
|
||||
"hygiene_prompt_version",
|
||||
"hygiene_schema_version",
|
||||
"enrichment_prompt_version",
|
||||
"enrichment_schema_version"
|
||||
],
|
||||
"properties": {
|
||||
"role_config_version": { "type": "string" },
|
||||
"provider": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"endpoint_url": { "type": "string", "format": "uri" },
|
||||
"timeout_seconds": { "type": "number", "minimum": 1 },
|
||||
"max_retries": { "type": "integer", "minimum": 0 },
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"hygiene_prompt_version": { "type": "string" },
|
||||
"hygiene_schema_version": { "type": "string" },
|
||||
"enrichment_prompt_version": { "type": "string" },
|
||||
"enrichment_schema_version": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"runtime_fallback": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"role_config_version",
|
||||
"provider",
|
||||
"model",
|
||||
"endpoint_url",
|
||||
"timeout_seconds",
|
||||
"max_retries",
|
||||
"parameters",
|
||||
"hygiene_prompt_version",
|
||||
"hygiene_schema_version",
|
||||
"enrichment_prompt_version",
|
||||
"enrichment_schema_version"
|
||||
],
|
||||
"properties": {
|
||||
"role_config_version": { "type": "string" },
|
||||
"provider": { "type": "string" },
|
||||
"model": { "type": "string" },
|
||||
"endpoint_url": { "type": "string", "format": "uri" },
|
||||
"timeout_seconds": { "type": "number", "minimum": 1 },
|
||||
"max_retries": { "type": "integer", "minimum": 0 },
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"hygiene_prompt_version": { "type": "string" },
|
||||
"hygiene_schema_version": { "type": "string" },
|
||||
"enrichment_prompt_version": { "type": "string" },
|
||||
"enrichment_schema_version": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"prompts": {
|
||||
"type": "object",
|
||||
"required": ["article_content_hygiene", "article_sentiment_tags"],
|
||||
"properties": {
|
||||
"article_content_hygiene": {
|
||||
"type": "object",
|
||||
"required": ["path", "version", "hash"],
|
||||
"properties": {
|
||||
"path": { "type": "string" },
|
||||
"version": { "type": "string" },
|
||||
"hash": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"article_sentiment_tags": {
|
||||
"type": "object",
|
||||
"required": ["path", "version", "hash"],
|
||||
"properties": {
|
||||
"path": { "type": "string" },
|
||||
"version": { "type": "string" },
|
||||
"hash": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ecp": {
|
||||
"type": "object",
|
||||
"required": ["canonical_schema_reference", "classifier_module"],
|
||||
"properties": {
|
||||
"canonical_schema_reference": { "type": "string" },
|
||||
"classifier_module": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"limits": {
|
||||
"type": "object",
|
||||
"required": ["max_input_bytes", "context_strategy"],
|
||||
"properties": {
|
||||
"max_input_bytes": {
|
||||
"type": "integer",
|
||||
"minimum": 1024,
|
||||
"description": "Maximum byte size of raw input article JSON payload before candidate extraction"
|
||||
},
|
||||
"context_strategy": {
|
||||
"type": "string",
|
||||
"const": "fail_before_provider",
|
||||
"description": "Strategy applied when input exceeds limit; strictly fails before calling provider"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"pricing": {
|
||||
"type": "object",
|
||||
"description": "Operational token pricing for cost reporting; does NOT alter functional execution fingerprint",
|
||||
"required": ["primary_input_1k", "primary_output_1k", "fallback_input_1k", "fallback_output_1k"],
|
||||
"properties": {
|
||||
"primary_input_1k": { "type": "number", "minimum": 0.0 },
|
||||
"primary_output_1k": { "type": "number", "minimum": 0.0 },
|
||||
"fallback_input_1k": { "type": "number", "minimum": 0.0 },
|
||||
"fallback_output_1k": { "type": "number", "minimum": 0.0 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"langfuse": {
|
||||
"type": "object",
|
||||
"required": ["environment", "trace_content_policy"],
|
||||
"properties": {
|
||||
"environment": { "type": "string" },
|
||||
"trace_content_policy": {
|
||||
"type": "string",
|
||||
"enum": ["metadata_only", "full_redacted"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sqlite": {
|
||||
"type": "object",
|
||||
"required": ["busy_timeout_ms"],
|
||||
"properties": {
|
||||
"busy_timeout_ms": { "type": "integer", "minimum": 100 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
Reference in New Issue
Block a user