mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
focus_owner: split FocusPolicy.kt 277 -> 238 + types
Moved the four declarations the policy is built from -- the schema version, PolicyFormatException, CurfewWindow with its midnight-wrapping contains(), and HomeLocation with its hasCoordinates guard -- to PolicyTypes.kt (48). Same package, so no call site and no import changed. Not the loader, though it is the bigger block: FocusPolicy.parse is called at eight sites, four of them in tests, and making it top-level would rewrite all of them. That also corrects an earlier reading of mine -- a declaration-name scan had `parse` down as untested, but every fixture in EnforcementFixtures.kt goes through it. The scan undercounts transitive exercise, so the seam, not the file percentage, is what decides these. 40 gradle tests and 94 flutter tests still pass, analyze clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
parent
b6a82093f2
commit
e6b9c730f2
@ -0,0 +1,50 @@
|
||||
{
|
||||
"intent": "Bring focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/FocusPolicy.kt (277 lines) under the repo-wide 250-line cap with no change to how a policy is parsed or applied.",
|
||||
"scope": [
|
||||
"focus_owner/.../FocusPolicy.kt — reduced to the FocusPolicy data class, its predicates and its companion-object loader",
|
||||
"focus_owner/.../PolicyTypes.kt — new: SUPPORTED_SCHEMA_VERSION, PolicyFormatException, CurfewWindow, HomeLocation",
|
||||
"Non-goal: any change to parsing, to the curfew wrap-around rule, or to the allowlist predicates",
|
||||
"Non-goal: DevicePolicyBridge.kt and EnforcementRunner.kt, which measurement rules out (see risks)"
|
||||
],
|
||||
"changes": [
|
||||
"Moved the four top-level declarations the policy is built from — the schema-version constant, the exception it raises, the CurfewWindow with its midnight-wrapping contains(), and the HomeLocation with its hasCoordinates guard — into PolicyTypes.kt.",
|
||||
"No call site changed and no import was added: both files are package com.kuhy.focus_owner, and Kotlin resolves top-level declarations across files in the same package. This is the same seam that worked for EnforcementDecision.kt earlier in this series.",
|
||||
"Deliberately did NOT extract the companion-object loader (load/loadFile/parse), even though it is the larger block. Callers write FocusPolicy.parse(...) at eight sites including four in the tests; moving it to a top-level function would have rewritten all of them, turning a zero-risk move into a real refactor."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew test --rerun-tasks",
|
||||
"result": "pass",
|
||||
"evidence": "BUILD SUCCESSFUL, and per-class counts unchanged: EnforcementDecisionCurfewTest 13, EnforcementDecisionTest 12, EnforcementLogTest 9, EnforcementRunnerBestTest 6 — 40 tests, 0 failures. --rerun-tasks again required, since a plain gradlew test reports UP-TO-DATE."
|
||||
},
|
||||
{
|
||||
"command": "flutter test && flutter analyze",
|
||||
"result": "pass",
|
||||
"evidence": "94 tests passed, no analyzer issues."
|
||||
},
|
||||
{
|
||||
"command": "pre-commit run --files on both files",
|
||||
"result": "pass",
|
||||
"evidence": "All configured hooks pass. Zero suppressions."
|
||||
},
|
||||
{
|
||||
"command": "wc -l on both files",
|
||||
"result": "pass",
|
||||
"evidence": "FocusPolicy.kt 238, PolicyTypes.kt 48 — both under the 250-line cap, down from a single 277-line file."
|
||||
},
|
||||
{
|
||||
"command": "Call-site check for the loader before choosing the seam",
|
||||
"result": "pass",
|
||||
"evidence": "grep found FocusPolicy.parse at four test sites and FocusPolicy.load at three production sites. This corrected an earlier reading: a declaration-name scan had counted `parse` as untested, but it is exercised by every fixture in EnforcementFixtures.kt. The proxy undercounts transitive exercise, which is why the seam question rather than the file-level percentage decided this split."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"FocusPolicy.kt at 238 lines has only 12 lines of headroom. Adding two or three more policy fields would put it back over the cap, at which point the loader extraction — and its eight call-site rewrites — becomes the next move.",
|
||||
"The remaining two over-cap Kotlin files are not split. DevicePolicyBridge.kt (415) references one tested declaration out of 28: it wraps Android's DevicePolicyManager, which JVM unit tests cannot instantiate, so any split there moves code no test executes. EnforcementRunner.kt (564) is exercised by six tests focused on one selection function; its location-acquisition and alarm paths need an instrumented device.",
|
||||
"focus_owner has no CI workflow and no Kotlin linter (no jacoco, ktlint or detekt), so the gradle and Flutter suites are the entire verifier for this change."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit — one edited file and one added file, with no data, config or schema migration.",
|
||||
"After rollback, re-run gradlew test --rerun-tasks and flutter test; expect 40 and 94 tests passing."
|
||||
]
|
||||
}
|
||||
@ -4,45 +4,6 @@ import android.content.Context
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
|
||||
/** Schema version this build understands. */
|
||||
const val SUPPORTED_SCHEMA_VERSION = 1
|
||||
|
||||
/** Thrown when the bundled policy cannot be trusted. */
|
||||
class PolicyFormatException(message: String) : Exception(message)
|
||||
|
||||
/** A nightly window that may wrap midnight (23:00 -> 05:00). */
|
||||
data class CurfewWindow(val startMinutes: Int, val endMinutes: Int) {
|
||||
/**
|
||||
* Whether [minutesSinceMidnight] falls inside the window.
|
||||
*
|
||||
* Wrapping windows are the normal case, so "inside" means at or after the
|
||||
* start *or* before the end, not both.
|
||||
*/
|
||||
fun contains(minutesSinceMidnight: Int): Boolean =
|
||||
if (startMinutes <= endMinutes) {
|
||||
minutesSinceMidnight in startMinutes until endMinutes
|
||||
} else {
|
||||
minutesSinceMidnight >= startMinutes || minutesSinceMidnight < endMinutes
|
||||
}
|
||||
}
|
||||
|
||||
/** The home anchor. Coordinates are null in a redacted policy. */
|
||||
data class HomeLocation(
|
||||
val latitude: Double?,
|
||||
val longitude: Double?,
|
||||
val radiusM: Double,
|
||||
val hysteresisM: Double,
|
||||
) {
|
||||
/**
|
||||
* Whether this policy carries real coordinates.
|
||||
*
|
||||
* The committed asset is redacted, so enforcement must check this rather
|
||||
* than treating a missing coordinate as (0, 0) — that would put "home" in
|
||||
* the Atlantic and silently disable the gate.
|
||||
*/
|
||||
val hasCoordinates: Boolean get() = latitude != null && longitude != null
|
||||
}
|
||||
|
||||
/**
|
||||
* The device-independent focus policy, generated from `config.sh`.
|
||||
*
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package com.kuhy.focus_owner
|
||||
|
||||
/**
|
||||
* The policy's value types: the schema version this build understands, the
|
||||
* failure it raises, and the two records [FocusPolicy] is built from.
|
||||
*
|
||||
* Same package as FocusPolicy, so nothing imports these explicitly and no call
|
||||
* site changes by their moving here.
|
||||
*/
|
||||
|
||||
/** Schema version this build understands. */
|
||||
const val SUPPORTED_SCHEMA_VERSION = 1
|
||||
|
||||
/** Thrown when the bundled policy cannot be trusted. */
|
||||
class PolicyFormatException(message: String) : Exception(message)
|
||||
|
||||
/** A nightly window that may wrap midnight (23:00 -> 05:00). */
|
||||
data class CurfewWindow(val startMinutes: Int, val endMinutes: Int) {
|
||||
/**
|
||||
* Whether [minutesSinceMidnight] falls inside the window.
|
||||
*
|
||||
* Wrapping windows are the normal case, so "inside" means at or after the
|
||||
* start *or* before the end, not both.
|
||||
*/
|
||||
fun contains(minutesSinceMidnight: Int): Boolean =
|
||||
if (startMinutes <= endMinutes) {
|
||||
minutesSinceMidnight in startMinutes until endMinutes
|
||||
} else {
|
||||
minutesSinceMidnight >= startMinutes || minutesSinceMidnight < endMinutes
|
||||
}
|
||||
}
|
||||
|
||||
/** The home anchor. Coordinates are null in a redacted policy. */
|
||||
data class HomeLocation(
|
||||
val latitude: Double?,
|
||||
val longitude: Double?,
|
||||
val radiusM: Double,
|
||||
val hysteresisM: Double,
|
||||
) {
|
||||
/**
|
||||
* Whether this policy carries real coordinates.
|
||||
*
|
||||
* The committed asset is redacted, so enforcement must check this rather
|
||||
* than treating a missing coordinate as (0, 0) — that would put "home" in
|
||||
* the Atlantic and silently disable the gate.
|
||||
*/
|
||||
val hasCoordinates: Boolean get() = latitude != null && longitude != null
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user