Files
Riskey 6aa0ae4661 feat: finish API reference Phase 1: code audit and readability overhaul (#857)
* feat: retire the merge pipeline and delete the legacy source specs

openapi_service.json is now the spec of record per language, edited directly. The build/relink modes, resolutions.json, and overrides/ that consolidated the five per-app-type specs are recoverable from git history when Phase 2 rebuilds the spec from the upstream-generated source.

* fix: make parity_check exit nonzero on failures

* docs: document the direct-edit spec workflow in skills and translation guides

* fix: correct the Service API reference against the code-verified audit

Applies the confirmed findings of a full per-operation audit against dify 3c8e0e2113 / graphon 0.6.0: 9 factual errors (availability lines, phantom fields, wrong enum values, inert parameters, wrong error messages), ~33 omitted-error and example-accuracy gaps, the rate_limit_error recharacterization, provider-identifier format documentation, and New Agent availability on the endpoints its Chat Features support (verified end-to-end in code).

* docs: document New Agent's full endpoint surface in the API guides

* translate: propagate the Service API audit fixes to zh and ja

* docs: rewrite Upload File as the readability pilot

Worked example of the API readability standard: contract-first description, facts on the fields (category size limits with env-var link, blacklist rule), real error triggers (415 corrected to its actual cause), template user sentence, source links on sourceable values, and a hand-written multipart cURL sample. Applied to en/zh/ja.

* docs: update spec-conventions for the live link scheme and code samples

* docs: apply the readability standard across the Service API reference

Rewrites all 82 operations to the agreed standard in en/zh/ja: contract-first descriptions, facts on the fields, trigger-led error bullets, template sentences (user, pagination, provider identifiers), source links on sourceable values, guide links instead of inline concept re-explanations, cURL samples where the playground autogen fails (multipart, SSE), a paragraph ceiling everywhere, and restructured streaming narratives (events-by-app-type map; details stay in the event tables). Also folds in two fact corrections surfaced during review: the legacy securitySchemes text and the stale Cloudflare-timeout claim in response_mode, now using the hedged edge-proxy wording.

* docs: codify the readability standard and audit method in the API skill

* fix: apply the audit-verified corrections deferred from the readability pass

Small factual items evidenced in the audit findings that the readability pass could not touch under its fact-freeze: real wire messages (audio 413, type_mismatch number, dynamic run-by-id 404, werkzeug 500/403 strings, annotation 404 punctuation), schema precision (format: uuid, minimum: 1, six missing Get Available Models response fields), and behavioral completeness (document download/zip/delete 404 message variants, tag_ids silent-ignore note, request-scoped batch-metadata lock wording, reasoning_chunk message_id: null, form default key absent not null). Applied to en/zh/ja; parity 0.

* fix: make spec checkers target openapi_service.json explicitly

After the legacy specs were deleted, the openapi_*.json globs in parity_check and lint_specs would silently pass (match nothing, report 0 issues, exit 0) if the spec of record went missing, and would pick up unintended future openapi_*.json files. Both now name openapi_service.json per language and exit 1 when it is absent.

* fix: address Copilot review on pipeline tooling and example titles

Normalizes example summary separators to the dominant "Request Example - Mode" form (the convention doc had drifted, and 4 em-dash outliers existed per language); lint_specs no longer re-reads the spec it already loaded; all spec reads use explicit UTF-8; coverage_matrix and swagger_diff target openapi_service.json explicitly; README and docstring usage resolve DOCS from the git root so the commands work from any directory.

* fix: use context managers for all pipeline file reads

Copilot flagged unclosed handles in the three checkers; applied consistently across all five pipeline scripts (merge_specs and coverage_matrix had the same pattern unflagged).

* fix: report missing spec ops as coverage failures instead of crashing

* fix: print the full relative path in the parity missing-file message
2026-07-11 22:00:14 +08:00

91 lines
3.4 KiB
Python

"""Diff the code-generated flask-restx swagger against the documented en specs.
Disagreements are flags for Tier 1 investigation, not verdicts: the restx doc
decorators are themselves hand-maintained.
"""
import json
import os
import re
import urllib.request
from collections import defaultdict
DOCS = os.environ["DOCS"]
SWAGGER_URL = os.environ.get("SWAGGER_URL", "http://localhost:15001/v1/swagger.json")
def blank(p):
return re.sub(r"\{[^}]+\}", "{}", p)
with urllib.request.urlopen(SWAGGER_URL, timeout=20) as _fh:
swagger = json.load(_fh)
print(f"swagger version: {swagger.get('swagger') or swagger.get('openapi')}, paths: {len(swagger['paths'])}")
code_ops = {}
for p, ms in swagger["paths"].items():
for m, op in ms.items():
if m not in ("get", "post", "put", "patch", "delete"):
continue
params = {}
for prm in op.get("parameters", []) or []:
if prm.get("in") in ("query", "path"):
params[(prm["name"], prm["in"])] = bool(prm.get("required"))
code_ops[(blank(p), m)] = {
"path": p,
"params": params,
"responses": set((op.get("responses") or {}).keys()),
"deprecated": bool(op.get("deprecated")),
}
spec_ops = {}
for f in [f"{DOCS}/en/api-reference/openapi_service.json"]:
with open(f, encoding='utf-8') as _fh:
spec = json.load(_fh)
name = f.split("/")[-1]
for p, ms in spec["paths"].items():
for m, op in ms.items():
if m not in ("get", "post", "put", "patch", "delete"):
continue
params = {}
for prm in op.get("parameters", []) or []:
if prm.get("in") in ("query", "path"):
params[(prm["name"], prm["in"])] = bool(prm.get("required"))
key = (blank(p), m)
e = spec_ops.setdefault(key, {"path": p, "params": {}, "responses": set(), "specs": []})
e["params"].update(params)
e["responses"] |= set((op.get("responses") or {}).keys())
e["specs"].append(name)
shared = sorted(set(code_ops) & set(spec_ops))
print(f"shared operations: {len(shared)}\n")
n = 0
for key in shared:
c, s = code_ops[key], spec_ops[key]
msgs = []
c_q = {name for (name, loc) in c["params"] if loc == "query"}
s_q = {name for (name, loc) in s["params"] if loc == "query"}
only_code = c_q - s_q
only_spec = s_q - c_q
if only_code:
msgs.append(f"query params in code-swagger but not documented: {sorted(only_code)}")
if only_spec:
msgs.append(f"query params documented but not in code-swagger: {sorted(only_spec)}")
for pk in sorted(set(c["params"]) & set(s["params"])):
if c["params"][pk] != s["params"][pk]:
msgs.append(f"required mismatch on {pk}: code={c['params'][pk]} spec={s['params'][pk]}")
resp_only_spec = {r for r in s["responses"] if r not in c["responses"] and r != "default"}
resp_only_code = {r for r in c["responses"] if r not in s["responses"] and r != "default"}
if resp_only_code:
msgs.append(f"status codes in code-swagger not documented: {sorted(resp_only_code)}")
if resp_only_spec:
msgs.append(f"status codes documented, absent from code-swagger: {sorted(resp_only_spec)}")
if msgs:
n += len(msgs)
print(f"== {key[1].upper()} {s['path']} ({', '.join(sorted(set(s['specs'])))})")
for msg in msgs:
print(" -", msg)
print(f"\nTOTAL FLAGS: {n}")