LockWatch Inconsistencies
--------------------------


Resume Bullet vs. Code Reality
--------------------------------

The resume bullet in PLAN.md (V3 Security/Infra) states LockWatch is "published to PyPI." The
package at version 0.1.0 is fully structured, the GitHub Actions publish workflow is written, and
the PyPI OIDC trusted publisher is configured in the workflow, but querying the PyPI simple index
for "lockwatch" returns HTTP 404. The package has not been published. The badge in README.md points
to badge.fury.io which will also show an error or stale data. Severity: high. This bullet cannot
be stated in an interview as fact until the tag is pushed and the publish workflow succeeds.


PLAN.md States RS256 but README Quickstart Shows HMAC Secret
--------------------------------------------------------------

The PLAN.md architecture section and the jwt_rotation.py source both implement RS256 with a PEM
private key (rsa_private_key_pem or rsa_private_key_path on JWTConfig). The interview narrative
in PLAN.md correctly describes RS256. However, the README.md JWT Rotation quickstart shows:
  JWTConfig(secret_key="your-secret", access_token_ttl=900)
The field secret_key does not exist on JWTConfig. The correct field is rsa_private_key_pem. The
field access_token_ttl also does not exist; the correct field is access_token_ttl_seconds. Any
developer who copies the README quickstart will get a TypeError. Severity: high. The README is
the canonical public documentation and this is the first thing a new user will read.


PLAN.md Claims HMAC with SHA-256 Stored Token Hash, Code Uses SHA-256 of Full JWT
----------------------------------------------------------------------------------

The PLAN.md Phase 3 specification says "Store SHA256(refresh_token) in Redis." The code does
exactly this. However, the PLAN.md architecture diagram describes the refresh token as "opaque" and
the interview narrative calls it an "opaque token stored in Redis." The refresh token is actually
a full RS256 signed JWT containing subject, issuer, audience, expiry, and type claims, not an
opaque random string. The token is not opaque to anyone who decodes the base64 JWT header and
payload. The security properties are equivalent (the token is still not usable without Redis
validation), but calling it "opaque" in an interview when it is a structured JWT would be
technically incorrect. Severity: low. The design is sound; the description is slightly imprecise.


PLAN.md Claims Atomic Rotation but Code Is Not Fully Atomic
-------------------------------------------------------------

The PLAN.md Phase 3 checklist says rotate() should "atomically swap old for new and blacklist the
old JTI." The README.md also states: "rotate() atomically swaps old for new and blacklists the old
refresh token." The code in jwt_rotation.py performs the token hash deletion and then calls
issue_token_pair() as two separate Redis operations without a MULTI/EXEC transaction. A race
condition exists where two concurrent rotate() calls with the same token could both succeed.
The code comment on line 156 explicitly acknowledges this: "The blacklist check + new issue is NOT
a Redis transaction." The PLAN.md also acknowledges it as a Phase 2 fix. Stating that rotation is
"atomic" in the resume bullet or README without qualification is inaccurate. Severity: medium.
The gap is acknowledged in the codebase, but the README marketing copy and resume bullet do not
reflect the qualification.


Flask Middleware Feature Gap
-----------------------------

The PLAN.md and README describe LockWatch as providing "sliding-window rate limiting, JWT rotation
with refresh token blacklisting, IP burst anomaly detection, and Postgres audit logging" as a
unified package. The FastAPI middleware implements all four. The Flask (WSGI) middleware implements
only rate limiting. It does not wire up the AnomalyDetector or AuditLogger. A Flask user gets one
of the four claimed features. Severity: medium. The README Flask quickstart example does not
mention this limitation, and a reader who only looks at the quickstart section would not know.


PyPI Badge in README Points to Unregistered Package
------------------------------------------------------

The README.md opens with a PyPI version badge: badge.fury.io/py/lockwatch.svg. Since the package
is not on PyPI, this badge will display an error state or a "version not found" message. When a
hiring engineer views the GitHub repository, the broken badge is immediately visible and undermines
the credibility of the "published to PyPI" claim. Severity: high for presentation quality. The fix
is either to publish the package before sharing the repo or to remove the badge until publication.
