Overview
Each item is an original, long-horizon development task in a real, actively maintained codebase. In one message, a developer asks for a capability the codebase does not have yet. The agent has to find its way around the code, locate every place that must change, implement the whole behaviour across many files (every parallel code path, default, edge case and error condition) and commit it. Each item has two parts: a re-runnable task environment in the DeepSWE format (a Harbor task directory: the Dockerfile of an offline environment, the request, hidden tests and a reference solution), ready for reinforcement learning; and full trajectories of Claude Opus 5.5 at max effort working on it in mini-swe-agent, the bash-only agent harness of the DeepSWE leaderboard, each with its grade and an audit of why it passed or failed, for supervised fine-tuning.
What you get
tasks/<task>/ # Harbor task directory: runs as is with Harbor or Pier
├── task.toml # metadata, resources, time limits; no network for agent or grader
├── instruction.md # the request: the agent's only input
├── environment/ # Dockerfile + build context (codebase at a fixed commit) for the offline image
├── tests/ # hidden: test.sh, hidden tests, fail-to-pass and regression lists, build/type-check gates
├── solution/ # reference solution: solution.patch, solve.sh
└── requirements.json # each requirement of the request <-> the hidden tests that check it
trials/<task>__<n>/
├── agent/
│ ├── requests.jsonl.zst # request-level API records: full context and reply of every call, thinking summaries
│ ├── trajectory.json # the same run in ATIF
│ ├── mini-swe-agent.trajectory.json
│ └── steps.jsonl # step view: thinking summary, command, output
├── artifacts/model.patch # the committed change that was graded
├── verifier/ # reward.json, ctrf.json, test output
├── result.json # run summary: model calls, commands, time
└── audit.json # why the run passed or failed; root cause of each failing test
index.json # tasks and trials
README.md # building, running and grading
- Grading: a run scores 1 when every fail-to-pass test passes and no regression test or build/type-check gate fails, otherwise 0; reward.json also records the pass fraction of each kind of test. The hidden tests go through the public interface the request names; the agent’s own test files are set aside while grading.
- Isolation: the agent works offline and never sees the hidden tests or the reference solution; grading runs in a fresh container on the committed change.
- Trajectories: each step holds a thinking summary, a bash command and its output; the request-level records keep the full context of every model call.
Samples
Eight tasks in five languages, each in a different field. Reference solutions add 364–2,221 lines across 8–30 files; each task has 84–1,464 hidden fail-to-pass tests, plus 335–22,002 regression tests. Trajectories run 57–190 model calls (31–120 minutes).
| Sample | Language | Field | Reference solution | Fail-to-pass tests |
|---|---|---|---|---|
| 1 | TypeScript | Web map drawing library | +2,115 lines / 26 files | 245 |
| 2 | Python | RDF graph library | +364 lines / 15 files | 508 |
| 3 | JavaScript | XML parser | +806 lines / 9 files | 84 |
| 4 | Python | ORM admin panel | +474 lines / 9 files | 209 |
| 5 | Go | Jira API client | +575 lines / 8 files | 495 |
| 6 | TypeScript | Cron scheduler | +1,399 lines / 18 files | 339 |
| 7 | Rust | Authorization policy engine | +1,527 lines / 10 files | 138 |
| 8 | Go | Go JSON library | +2,221 lines / 30 files | 1,464 |
Each card shows the request exactly as the agent received it, what makes the task hard, what the hidden tests check, key moments of the trajectory and what the agent committed; an excerpt of the change is folded at the end.
Sample 1 of 8
Add snapping.toGrid grid snapping to Terra Draw modes
Terra Draw, a TypeScript monorepo for drawing and editing GeoJSON features on web maps, with adapters for MapLibre, Leaflet, OpenLayers, Mapbox, Google Maps and ArcGIS.
Add a declarative grid-snapping strategy: an exported SnapToGrid type and a snapping.toGrid option that snaps to a lattice in degrees or Web Mercator meters. The grid applies at lowest priority in point, marker, linestring, polyline and polygon modes, gives rectangle mode its first snapping option, and covers select-mode vertex drags.
Why it is hard
- Grid snapping must reach six drawing modes plus select-mode vertex drags, sitting behind up to five existing strategies; rectangle mode had no snapping at all.
- Exact numeric semantics: halves round toward +infinity, meter grids go through Web Mercator relative to the projected origin, then results round to adapter precision.
- Control-flow edge rules: out-of-range snaps are skipped, not clamped; invalid configs silently disable the grid; clicks snapping onto the last committed vertex are ignored.
- Select mode must snap vertex drags only, not whole-feature, midpoint, resize, rotate or scale drags, while all 1,833 existing tests keep passing.
The request, verbatim (the agent’s only input)
Terra Draw's snapping option already snaps to coordinates, lines, features, angles (toDegree) and custom positions. For floor-plan editors I keep hand-rolling a lattice with toCustom, and rectangle mode can't snap at all, so I'd like a declarative grid strategy.
Please add a SnapToGrid type exported from terra-draw next to SnapToDegree: { size: number; units?: "degrees" | "meters"; origin?: Position }, with units defaulting to "degrees" and origin to [0, 0] (lng/lat). Add toGrid?: SnapToGrid to OneDimensionalSnapping (and therefore Snapping), honoured by TerraDrawPointMode, TerraDrawMarkerMode, TerraDrawLineStringMode, TerraDrawPolyLineMode and TerraDrawPolygonMode in the constructor and via draw.updateModeOptions(mode, { snapping }). Rules:
- Degrees: lng snaps to
origin[0] + Math.round((lng - origin[0]) / size) * size, lat likewise withorigin[1](exact halves round towards +infinity). - Meters: project the position and the origin to spherical Web Mercator x/y (EPSG:3857, R = 6378137, as the library's web-mercator conversion does), snap x and y with the same formula against the origin's x/y, then convert back to lng/lat.
- The snapped position is then rounded to the adapter's coordinate precision.
- If the snapped lng is outside [-180, 180] or lat outside [-90, 90], the grid doesn't apply to that pointer event.
- Invalid config silently disables the grid (no error):
sizenot a finite number > 0, unknownunits, ororiginnot two finite numbers. - Lowest priority: the grid is used only when no other enabled strategy (
toCustom,toFeature,toCoordinate,toLine,toDegree) yields a coordinate for the event.
In point and marker modes, points created by left click and points moved by editable: true drags land on the snapped position. In linestring, polyline and polygon modes, every committed vertex (including the first click) and the provisional mouse-move vertex use the snapped position, and while the grid applies on mouse move a snappingPoint: true guidance Point is shown there, managed like the other strategies' snapping points. Closing/finishing detection keeps using the pointer's screen position, and a click that snaps onto the previously committed vertex is ignored, as identical coordinates are today.
TerraDrawRectangleMode gets a new snapping?: { toGrid?: SnapToGrid } option (constructor and updateModeOptions). With it, in both the click-move and click-drag interactions, the start corner and the opposite corner are snapped (the latter on every pointer update and on finish), so all four corners of the finished rectangle lie on the grid. No snappingPoint guidance feature is created.
In TerraDrawSelectMode, flags[mode].feature.coordinates.snappable may be a Snapping object containing toGrid: with coordinates.draggable: true, dragging a vertex of a selected feature puts it on the snapped position (for a polygon's first/last vertex, both positions). Only vertex drags snap; whole-feature drags, midpoints, resize, rotate and scale don't.
updateModeOptions must be able to enable, change or remove toGrid at runtime, replacing the snapping object wholesale as now. Without toGrid (or a rectangle snapping option) every mode behaves exactly as before, and all new type fields stay optional.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- SnapToGrid is exported, and point, marker, linestring, polyline and polygon modes take snapping.toGrid from the constructor or updateModeOptions.
- Tests enable, change and remove the grid at runtime in point, marker, linestring, polyline, polygon and rectangle modes, and enable it for select-mode vertex drags.
- Degree math (default units and origin, offset origins, negative coordinates, halves rounding toward +infinity), meter grids measured from the projected origin, and coordinate-precision rounding.
- Linestring, polyline and polygon snap first clicks, provisional and committed vertices; in all three, a snappingPoint guidance Point follows the snapped position while drawing.
- Rectangles snap both corners, even when a click-drag ends without intermediate drags; select-mode vertex drags and editable: true point drags also snap.
- A meters grid snapping past the antimeridian is not applied, an unknown units value disables the grid silently, and behaviour without a grid is unchanged.
- fail-to-pass tests
- 245
- regression tests
- 1,833
- build/type-check gate
- 1
- files in the reference solution
- 26
- lines it adds
- +2,115
Key moments of the trajectory
Calls 8–10 Looked up the commit that added toDegree degree snapping and used it as the template for the new type, behavior class and exports.
Calls 27–28, 42–43, 45 Confirmed closing detection uses the pointer’s screen position, and that polygon mode already skips identical clicks while linestring and polyline do not, so those two needed a guard for grid-snapped clicks onto the last vertex.
Calls 51, 53–55, 69 Established a green baseline in the core terra-draw package (95 suites, 1,673 tests) under both Jest configs, created feat/snap-to-grid, and wrote a temporary repro spec whose four cases all failed.
Calls 73, 75, 77–78, 80–84 Wrote GridSnappingBehavior (validation, degree and meter math, precision rounding, bounds check), made it the lowest-priority fallback in five drawing modes, added the rectangle option, and wired select vertex drags, excluding midpoint-inserted ones.
Calls 96–98 When the agent’s own precision test failed, it checked the arithmetic: 1.2345 snaps to 1.235 before rounding to 1.24, so the expectation was wrong; it fixed the test and added a snap-then-round case.
Calls 118–122 Printing the geometry after each step of its select-mode non-vertex-drag test, the agent found rotate and scale changed nothing, so it rewrote the test to send two drag events and scale from a vertex.
Calls 129, 137 Confirmed with a throwaway spec that the type-checked Jest config fails on units: "feet", so its passes are meaningful, then ran the full terra-draw package suite with coverage thresholds: 96 suites, 1,768 tests passed.
Calls 149–150, 154, 156, 175–176 Found a stale insert-coordinates edge case when the pointer snaps back onto the last committed vertex and fixed it for grid snapping only, then committed everything and re-ran the terra-draw package suite: 1,769 passed.
What the agent committed
- model calls
- 178
- bash commands
- 187
- minutes
- 70
- files changed
- 29
- lines added / removed
- +2,407 / −39
On the feat/snap-to-grid branch the agent committed a SnapToGrid type and a shared GridSnappingBehavior. The behavior validates config, snaps in degrees or EPSG:3857 meters, rounds to coordinate precision and skips out-of-range results; it is wired into point, marker, linestring, polyline and polygon modes, a new rectangle snapping option and select-mode vertex drags. The agent also added tests of its own.
The agent's committed change (excerpt)
diff --git a/packages/terra-draw/src/modes/grid-snapping.behavior.ts b/packages/terra-draw/src/modes/grid-snapping.behavior.ts
new file mode 100644
--- /dev/null
+++ b/packages/terra-draw/src/modes/grid-snapping.behavior.ts
@@ -0,0 +1,142 @@
+import { Position } from "geojson";
+import { SnapToGrid, TerraDrawMouseEvent } from "../common";
+import { isNonNullObject } from "../common/checks";
+import {
+ validLatitude,
+ validLongitude,
+} from "../geometry/boolean/is-valid-coordinate";
+import { limitPrecision } from "../geometry/limit-decimal-precision";
+import {
+ lngLatToWebMercatorXY,
+ webMercatorXYToLngLat,
+} from "../geometry/project/web-mercator";
+import { BehaviorConfig, TerraDrawModeBehavior } from "./base.behavior";
+
+type GridUnits = NonNullable<SnapToGrid["units"]>;
+
+type ValidGrid = {
+ size: number;
+ units: GridUnits;
+ origin: [number, number];
+};
+
+const DEFAULT_UNITS: GridUnits = "degrees";
+
+const isFiniteNumber = (value: unknown): value is number =>
+ typeof value === "number" && Number.isFinite(value);
+
+/**
+ * Snaps positions to a regular grid (lattice), either in degrees of
+ * longitude/latitude or in meters on the spherical Web Mercator plane.
+ */
+export class GridSnappingBehavior extends TerraDrawModeBehavior {
+ constructor(readonly config: BehaviorConfig) {
+ super(config);
+ }
+
+ /**
+ * Returns the grid position nearest to the event, rounded to the coordinate
+ * precision. Returns undefined if the grid options are invalid, or if the
+ * snapped position falls outside of valid longitude/latitude bounds, in which
+ * case the grid does not apply to the event.
+ */
+ public getSnappableCoordinate(
+ event: Pick<TerraDrawMouseEvent, "lng" | "lat">,
+ options: SnapToGrid | undefined,
+ ): Position | undefined {
+ const grid = this.getValidGrid(options);
+
+ if (!grid) {
+ return undefined;
+ }
+
+ const [snappedLng, snappedLat] =
+ grid.units === "meters"
+ ? this.snapMeters(event, grid)
+ : this.snapDegrees(event, grid);
+
+ const lng = this.limitPrecision(snappedLng);
+ const lat = this.limitPrecision(snappedLat);
+
+ if (!validLongitude(lng) || !validLatitude(lat)) {
+ return undefined;
+ }
+
+ return [lng, lat];
+ }
+
+ private snapDegrees(
+ { lng, lat }: Pick<TerraDrawMouseEvent, "lng" | "lat">,
+ { size, origin }: ValidGrid,
+ ): Position {
+ return [
+ this.snapValue(lng, origin[0], size),
+ this.snapValue(lat, origin[1], size),
+ ];
+ }
+
+ private snapMeters(
+ { lng, lat }: Pick<TerraDrawMouseEvent, "lng" | "lat">,
+ { size, origin }: ValidGrid,
+ ): Position {
+ const { x, y } = lngLatToWebMercatorXY(lng, lat);
+ const { x: originX, y: originY } = lngLatToWebMercatorXY(
+ origin[0],
+ origin[1],
+ );
+
+ const snapped = webMercatorXYToLngLat(
+ this.snapValue(x, originX, size),
+ this.snapValue(y, originY, size),
+ );
+
+ return [snapped.lng, snapped.lat];
+ }
+
+ /**
+ * Snaps a single value to the nearest grid line. Math.round means
+ * that exact halves round towards +infinity.
+ */
+ private snapValue(value: number, origin: number, size: number) {
+ return origin + Math.round((value - origin) / size) * size;
+ }
+
+ private limitPrecision(value: number) {
+ const limited = limitPrecision(value, this.coordinatePrecision);
+
+ // Normalise negative zero, which can result from rounding
+ // tiny negative values, so that coordinates are always [0, 0]
+ return limited === 0 ? 0 : limited;
+ }
+
+ /**
+ * Validates the grid options, applying defaults. Invalid options
+ * return undefined which silently disables grid snapping.
+ */
+ private getValidGrid(options: unknown): ValidGrid | undefined {
+ if (!isNonNullObject(options)) {
+ return undefined;
+ }
+
+ const { size, units = DEFAULT_UNITS, origin = [0, 0] } = options;
+
+ if (!isFiniteNumber(size) || size <= 0) {
+ return undefined;
+ }
… 3,133 more lines not shown (29 files changed in all)
Sample 2 of 8
Add RDF 1.2 base-direction literals ("x"@ar--rtl) end to end
rdflib, a Python library for working with RDF: a graph and term model, parsers and serializers for N-Triples, Turtle, TriG, N3 and other formats, a pyparsing-based SPARQL 1.1 engine, and pluggable stores.
Make RDF 1.2 directional language-tagged strings ("x"@ar--rtl) work end to end. That covers a direction on Literal, the N-Triples and Turtle-family parsers and serializers, SPARQL syntax plus LANGDIR, hasLANG, hasLANGDIR and STRLANGDIR, SPARQL result formats, and stores, while non-directional literals behave exactly as before.
Why it is hard
- Many parallel paths must agree:
Literal, line-based and Notation3 parsers, seven serializers, SPARQL grammar and functions, result formats, stores; the reference touches 15 files. - Bad directions (uppercase, other text after
--, or--rtlwithout a tag) must raiseParserErrorin nt/nquads andBadSyntaxin turtle/trig/n3, across string forms. - SPARQL must keep the direction wherever a literal appears (triple patterns, VALUES, BIND, CONSTRUCT templates, INSERT/DELETE DATA and WHERE-based updates), through both
Graph.query/Graph.updateandprepareQuery/prepareUpdate. - Non-directional literals must behave exactly as today, guarded by 9,327 pass-to-pass checks (existing tests plus build/type-check gates), though equality, hashing and pickling gain a field.
The request, verbatim (the agent’s only input)
rdflib handles "x"@ar everywhere, but not RDF 1.2's base direction ("x"@ar--rtl, rdf:dirLangString): such data won't even load. Please add it end to end.
Literal gets a fifth parameter direction=None and a read-only .direction; Literal('x', lang='ar', direction='rtl') has .language 'ar' and .datatype None.
Parsers nt/ntriples/nt11, nquads, turtle/ttl, trig and n3 accept the syntax, with subtags (@en-US--ltr), after every Turtle/TriG string form and N3's double-quoted ones. Serializers nt, nt11, nquads, turtle, longturtle, trig and n3 write it (N-Triples line: <http://e/s> <http://e/p> "x"@ar--rtl .) and round-trip. SPARQL accepts it wherever a literal may appear, in Graph.query/Graph.update and prepareQuery/prepareUpdate.
directionmust be None,'ltr'or'rtl'(elseValueError, even for''); a direction without a language tag raisesTypeError.==, hash andeq()(so SPARQL=,!=,IN,sameTerm) distinguish"x"@ar,"x"@ar--ltrand"x"@ar--rtl; a Graph holds all three.repr()isrdflib.term.Literal('x', lang='ar', direction='rtl');n3()is"x"@ar--rtl(also with a namespace_manager or when triple-quoted), andrdflib.util.from_n3parses it back. pickle, copy, deepcopy andLiteral(lit)preserve it.- Only lowercase
ltr/rtlmay follow--; anything else, or--rtlwith no language tag, raisesrdflib.exceptions.ParserError(nt, nquads) orrdflib.plugins.parsers.notation3.BadSyntax(turtle, trig, n3). LANGDIRgives"ltr"/"rtl",""for other literals, an error (like LANG) for IRIs/bnodes.hasLANG/hasLANGDIRgive xsd:boolean, true iff given a literal with a tag/direction respectively (never an error).STRLANGDIR("abc", "ar", "rtl")is"abc"@ar--rtl, an error for a bad direction or tagged/typed first argument.DATATYPE("x"@ar--rtl)is rdf:dirLangString as a plain URIRef (not added tordflib.RDF); LANG returns just the tag.- UCASE, LCASE, SUBSTR, STRBEFORE, STRAFTER and REPLACE keep the direction where they keep the tag; CONCAT keeps tag and direction only if all arguments share both, else neither.
Result.serialize adds "its:dir": "rtl" beside "xml:lang" in json, and a dir attribute in namespace http://www.w3.org/2005/11/its on <literal> in xml; Result.parse reads both back and accepts "x"@ar--rtl in tsv cells; txt prints --rtl. Memory and BerkeleyDB stores keep the direction across re-open.
Non-directional literals must behave and serialize exactly as today; JSON-LD keeps dropping @direction.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- Literal: fifth positional direction, read-only property, ValueError/TypeError checks, equality, hashing and Graph storage keeping three variants apart, repr, n3(), from_n3, pickle and copies.
- Parsers: nt, nquads, turtle, trig, n3 accept @ar--rtl in each format's string forms, plus subtags (@en-US--ltr); N-Quads keeps graph names; bad directions raise ParserError/BadSyntax.
- Serializers: exact N-Triples/N-Quads lines with escaping, and round-trips through nt, nt11, nquads, turtle, longturtle, trig and n3, including multiline strings.
- SPARQL queries and updates: directional literals in triple patterns, VALUES, BIND, SELECT expressions, CONSTRUCT, ASK, INSERT/DELETE DATA, DELETE/INSERT WHERE and named graphs.
- SPARQL functions: LANGDIR, hasLANG, hasLANGDIR, STRLANGDIR (with COALESCE fallback on errors), DATATYPE, LANG, string functions and CONCAT tag/direction rules.
- Results, stores, end-to-end: JSON its:dir and XML ITS dir round-trips, TSV cells, TXT output; Memory/BerkeleyDB re-open; JSON-LD still dropping @direction; Turtle→query→JSON/XML results; N-Triples→STRLANGDIR update→serialize; TriG↔N-Quads.
- fail-to-pass tests
- 508
- regression tests
- 9,325
- build/type-check gates
- 2
- files in the reference solution
- 15
- lines it adds
- +364
Key moments of the trajectory
Calls 2–3, 9, 15 Created branch rdf12-base-direction from main first, then mapped term.py; finding rdflib.RDF is a strict DefinedNamespace, it planned rdf:dirLangString as a plain URIRef constant, then mapped the parsers and serializers.
Calls 46–47, 53, 55 A full test run hit the command runner’s 30-second limit, so the agent moved it to a background job, polled the log and recorded the pre-change baseline.
Calls 57, 59, 62, 66, 70–73, 77 Threaded direction through Literal (validation, __eq__, hash, eq(), ordering, n3(), repr, pickling), then confirmed that pickles of non-directional literals are byte-identical to before.
Calls 81–82, 84, 86 The N-Triples regex and Notation3 parser now capture any alphanumeric run after --, so invalid directions such as --RTL or --rtl2 raise ParserError / BadSyntax. Then tested all Turtle/TriG/N3 string forms.
Calls 90–91, 93 Checked translateAlgebra’s endswith() chain so the new builtins aren’t mistaken for LANG, and renamed the new SPARQL grammar terminal from LANGDIR to BASE_DIRECTION to avoid confusion with the LANGDIR() builtin.
Calls 107–111 Probed each STRLANGDIR / LANGDIR error case individually, then checked JSON/XML/TSV/TXT results (including the non-lxml and stdlib-json paths) and Memory/BerkeleyDB stores across close and re-open.
Calls 131–132 Tested its docs claim that RDF/XML ignores its:dir on parse. The parser treats it as a property attribute, so the docs now say such formats don’t support base directions and drop them on output.
Calls 170, 179, 181 Final checks: the full suite matched the pre-change baseline, and stashing the work showed that unmodified main rejects "x"@ar--rtl. Code, docs and its own tests went into one commit on the branch.
What the agent committed
- model calls
- 184
- bash commands
- 191
- minutes
- 47
- files changed
- 19
- lines added / removed
- +1,493 / −35
The agent added base direction to Literal, the N-Triples and Notation3 parsers, the serializers, the SPARQL grammar and functions, and the SPARQL JSON/XML/TSV result formats, plus docs and 253 tests of its own, all committed on branch rdf12-base-direction.
The agent's committed change (excerpt)
diff --git a/rdflib/term.py b/rdflib/term.py
--- a/rdflib/term.py
+++ b/rdflib/term.py
@@ -110,6 +110,17 @@ def _is_valid_langtag(tag: str) -> bool:
return bool(_lang_tag_regex.match(tag))
+# RDF 1.2 base directions of directional language-tagged strings, see
+# https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction
+_VALID_DIRECTIONS = frozenset(("ltr", "rtl"))
+
+
+def _is_valid_direction(direction: Any) -> bool:
+ """Is `direction` a valid RDF 1.2 base direction (exactly `ltr` or `rtl`)?"""
+ # str() so that str subclasses (e.g. a Literal "rtl") are judged by value
+ return isinstance(direction, str) and str(direction) in _VALID_DIRECTIONS
+
+
def _is_valid_unicode(value: Union[str, bytes]) -> bool:
"""
Verify that the provided value can be converted into a Python
@@ -532,6 +543,24 @@ class Literal(Identifier):
A literal is a language-tagged string if the third element is present. Lexical representations of language tags MAY be converted to lower case. The value space of language tags is always in lower case.
+ RDF 1.2 adds an optional fourth element to language-tagged strings, a
+ [base direction](https://www.w3.org/TR/rdf12-concepts/#dfn-base-direction)
+ which is either `ltr` or `rtl`. Such a *directional language-tagged
+ string* has the datatype `rdf:dirLangString`, it is written as
+ `"..."@ar--rtl` in N-Triples, Turtle, SPARQL etc. and is created with the
+ `direction` parameter:
+
+ ```python
+ >>> lit = Literal("hello", lang="ar", direction="rtl")
+ >>> lit.language, lit.direction, lit.datatype
+ ('ar', 'rtl', None)
+ >>> lit.n3()
+ '"hello"@ar--rtl'
+ >>> lit == Literal("hello", lang="ar")
+ False
+
+ ```
+
---
For valid XSD datatypes, the lexical form is optionally normalized
@@ -631,7 +660,8 @@ class Literal(Identifier):
# NOTE: _datatype should maybe be of type URIRef, and not optional.
_datatype: Optional[URIRef]
_ill_typed: Optional[bool]
- __slots__ = ("_language", "_datatype", "_value", "_ill_typed")
+ _direction: Optional[str]
+ __slots__ = ("_language", "_datatype", "_value", "_ill_typed", "_direction")
def __new__(
cls,
@@ -639,8 +669,26 @@ class Literal(Identifier):
lang: Optional[str] = None,
datatype: Optional[str] = None,
normalize: Optional[bool] = None,
+ direction: Optional[str] = None,
):
- """Create a new Literal instance."""
+ """Create a new Literal instance.
+
+ Args:
+ lexical_or_value: The lexical form (a `str`), a Python value to
+ convert, or another `Literal` to copy.
+ lang: An optional language tag.
+ datatype: An optional datatype IRI, mutually exclusive with `lang`.
+ normalize: Whether to normalize the lexical form of recognized
+ datatypes, defaults to `rdflib.NORMALIZE_LITERALS`.
+ direction: An optional RDF 1.2 base direction, `"ltr"` or `"rtl"`;
+ only allowed together with a language tag.
+
+ Raises:
+ TypeError: If both `lang` and `datatype` are given, or if a
+ `direction` is given without a language tag.
+ ValueError: If `lang` is not a valid language tag or `direction`
+ is not `"ltr"` or `"rtl"`.
+ """
if lang == "":
lang = None # no empty lang-tags in RDF
@@ -655,6 +703,25 @@ class Literal(Identifier):
if lang is not None and not _is_valid_langtag(lang):
raise ValueError(f"'{str(lang)}' is not a valid language tag!")
+ if direction is not None:
+ if not _is_valid_direction(direction):
+ raise ValueError(
+ f"{direction!r} is not a valid base direction, "
+ "it must be 'ltr' or 'rtl'"
+ )
+ direction = str(direction)
+ if datatype is not None or (
+ lang is None
+ and not (
+ isinstance(lexical_or_value, Literal) and lexical_or_value.language
+ )
+ ):
+ raise TypeError(
+ "A Literal can only have a base direction if it has a "
+ "language tag (and no datatype), "
+ "per https://www.w3.org/TR/rdf12-concepts/#section-Graph-Literal"
+ )
+
if datatype is not None:
datatype = URIRef(datatype)
@@ -664,6 +731,9 @@ class Literal(Identifier):
# create from another Literal instance
lang = lang or lexical_or_value.language
+ if direction is None and lang and datatype is None:
+ # keep the base direction of the copied (language-tagged) literal
+ direction = lexical_or_value.direction
if datatype is not None:
# override datatype
value = _castLexicalToPython(lexical_or_value, datatype)
@@ -699,6 +769,7 @@ class Literal(Identifier):
lexical_or_value = _value
if datatype is not None:
lang = None
+ direction = None
if isinstance(lexical_or_value, bytes):
lexical_or_value = lexical_or_value.decode("utf-8")
… 2,025 more lines not shown (19 files changed in all)
Sample 3 of 8
Namespace-aware XML parsing: resolve names by namespace URI
fast-xml-parser (JavaScript, v5.11.2), a library that validates XML, parses it into JS objects and builds XML from them.
Add an opt-in resolveNamespaces option to XMLParser that tracks xmlns scopes and names elements and attributes by namespace URI (caller-supplied alias, xml:local or {uri}local), so results no longer depend on the prefix a producer picked. With the option unset or false nothing may change, and the work goes on a new branch from main and must be fully committed.
Why it is hard
- Scope rules have many edges: inner overrides,
xmlns=""unsetting, the fixedxmlprefix, and declarations on self-closing, unpaired or stop-node elements staying local. - jPaths must match across five callbacks and the matcher, yet for prefixed tags existing code doubles the prefix in some callbacks but not in
isArray. xmlnsdeclarations must vanish from output and attribute callbacks yet still bind whenignoreAttributesdrops them; unbound attribute prefixes must throw even then.- Every option and callback must see resolved names, with
unpairedTags/stopNodesmatched aftertransformTagName, yet a stop node’s end tag must still be matched as written.
The request, verbatim (the agent’s only input)
XMLParser either keeps namespace prefixes or strips them (removeNSPrefix), so code keyed on soap:Envelope breaks once a producer picks another prefix. Please add an opt-in resolveNamespaces parser option (boolean | Record<string, string>, default false, typed in X2jOptions) that tracks xmlns scopes and resolves names by namespace URI; when unset or false, nothing changes.
xmlns="U"(default namespace) andxmlns:p="U"(prefixp) apply to the declaring element (name, attributes, end tag) and its descendants; inner declarations override outer ones,xmlns=""unsets the default, andxmlis alwayshttp://www.w3.org/XML/1998/namespace. Declarations on self-closing, unpaired or stop-node elements affect only that element.- Unprefixed element names are in the in-scope default namespace (if any);
p:local(split at the first colon) is in the namespace bound top. Unprefixed attributes are never namespaced; prefixed ones resolve like elements. - Naming (compact and
preserveOrderalike): no namespace → local name; namespaceU→alias:localif the option object mapsU(own key) toalias(localifaliasis""), elsexml:localfor the XML namespace, else{U}local;trueacts like{}. xmlns/xmlns:*declarations are not attributes: never in output (@_keys,attributesGroupNamegroups,:@maps), never passed to theignoreAttributesfunction,transformAttributeNameorattributeValueProcessor, yet honoured even ifignoreAttributesistrueor would filter them.- An unbound prefix on an element or attribute makes
parse()throw anErrorwith message exactlyUndeclared namespace prefix: <prefix>.
Every option and callback sees resolved names: transformTagName and updateTag get the element name, transformAttributeName gets attributeNamePrefix + attribute name, and unpairedTags/stopNodes match the resolved, then transformTagName-transformed name. jPaths use resolved names (e.g. soap:Envelope.soap:Body) and are identical for an element in updateTag, tagValueProcessor, attributeValueProcessor, ignoreAttributes and isArray; with jPath: false the matcher's getCurrentTag()/toString() agree. A stop node's name and attributes are resolved; its content stays raw and its end tag is matched as written. When enabled, removeNSPrefix is ignored, processing instructions (incl. <?xml ...?>) and their attributes are untouched, bindings never leak between parse() calls, and elements resolving to the same name group into arrays as usual.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- resolveNamespaces typed in X2jOptions; unset or false changes nothing (prefixes and xmlns attributes kept, removeNSPrefix applied); existing regression tests plus build/type-check gates guard against regressions.
- Scope rules: declarations cover the declaring element and descendants, inner ones override until closed, xmlns="" unsets, xml is built in, self-closing/unpaired/stop-node declarations stay local.
- Naming: plain local name without namespace, alias:local via own-key aliases (bare local for """"), else xml:local or {uri}local; true equals "{}"; same under preserveOrder.
- xmlns declarations never appear in "@_" keys, attribute groups or ":@" maps nor reach attribute callbacks, yet bind under any ignoreAttributes setting.
- Unbound element or attribute prefixes throw exactly Undeclared namespace prefix: <prefix>, also with preserveOrder or ignored attributes; aliases in the option never bind prefixes.
- Options and callbacks receive resolved names: name transforms, updateTag, value processors, isArray, unpairedTags, stopNodes, entity filters; plus a cross-prefix SOAP end-to-end test.
- fail-to-pass tests
- 84
- regression tests
- 335
- build/type-check gates
- 2
- files in the reference solution
- 9
- lines it adds
- +806
Key moments of the trajectory
Calls 16–17 Probing the unmodified parser showed that pushing a prefixed tag plus its namespace into the matcher doubles the prefix in updateTag and value-processor jPaths but not in isArray; resolve mode pushes only resolved names.
Calls 22–23 An experiment confirmed the legacy path applies transformTagName twice to self-closing tags (an x_-prefixing transform turned <a/> into x_x_a); resolve mode strips the / first and transforms once, keeping the legacy path as is.
Calls 28, 32 After cutting feat/resolve-namespaces from main, the agent wrote src/xmlparser/namespaces.js: immutable copy-on-write scopes, first-colon splitting, own-key alias lookup, the fixed xml binding and the exact unbound-prefix error.
Calls 38, 40, 43 Mid-refactor, 245 of 332 specs failed on a not-yet-defined readNamespacedStartTag; after defining it and wiring parseXml, the SOAP repro gave identical output for soap:, env: and default-namespace envelopes, with all existing specs green.
Calls 52–54 Against a pristine copy of main, with the option unset or false, a differential harness found 0 mismatches in 4,800 runs (callback logs included); forcing the option on yielded 4,224, confirming its sensitivity.
Calls 61, 63, 74–75 After typing the option in both declaration files, the agent showed its tests discriminate: the typings test fails on main’s types and 35 of 39 new specs fail on main (the rest cover option-off behaviour).
Calls 90–91 Fuzzing against an independently written naming model first flagged about 2,000 of 3,000 documents, all attribute key-order artefacts; with object key order ignored (array order still checked), 15,000 random documents (five runs of 3,000) passed.
Calls 97–100 The agent committed nine files (+839/−67) as one commit on feat/resolve-namespaces, confirmed a clean tree with main untouched, and submitted after a summary without a tool call drew a format error.
What the agent committed
- model calls
- 100
- bash commands
- 108
- minutes
- 31
- files changed
- 9
- lines added / removed
- +839 / −67
The agent added src/xmlparser/namespaces.js (copy-on-write xmlns scopes, URI-based naming) and wired it into OrderedObjParser.js, adding the resolveNamespaces: false default in OptionsBuilder.js. It also typed the option in src/fxp.d.ts and lib/fxp.d.cts, documented it and added 40 specs, all in one commit on a new branch.
The agent's committed change (excerpt)
diff --git a/src/xmlparser/OrderedObjParser.js b/src/xmlparser/OrderedObjParser.js
--- a/src/xmlparser/OrderedObjParser.js
+++ b/src/xmlparser/OrderedObjParser.js
@@ -10,6 +10,7 @@ import { Expression, Matcher } from 'path-expression-matcher';
import { ExpressionSet } from 'path-expression-matcher';
import { EntityDecoder, XML, CURRENCY, COMMON_HTML, ENTITY_ACTION } from '@nodable/entities';
import { isUnsafe, HTML as HTML_CONTEXT, XML as XML_CONTEXT } from "is-unsafe"
+import { getNamespaceAliases, createRootScope, isNamespaceDeclaration, declareNamespaces, resolveQName } from './namespaces.js';
// const regx =
@@ -79,12 +80,22 @@ export default class OrderedObjParser {
this.parseTextData = parseTextData;
this.resolveNameSpace = resolveNameSpace;
this.buildAttributesMap = buildAttributesMap;
+ this.buildAttributes = buildAttributes;
+ this.readNamespacedStartTag = readNamespacedStartTag;
this.isItStopNode = isItStopNode;
this.replaceEntitiesValue = replaceEntitiesValue;
this.readStopNodeData = readStopNodeData;
this.saveTextToParentTag = saveTextToParentTag;
this.addChild = addChild;
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
+ // resolveNamespaces: namespace URI to alias map when enabled, null otherwise
+ this.nsAliases = getNamespaceAliases(this.options.resolveNamespaces);
+ // removeNSPrefix is ignored when names are resolved by namespace URI
+ this.removeNSPrefix = this.options.removeNSPrefix && this.nsAliases === null;
+ this.decodeNamespaceUri = (value) => {
+ const entityConfig = this.options.processEntities;
+ return entityConfig && entityConfig.enabled ? this.entityDecoder.decode(value) : value;
+ };
this.entityExpansionCount = 0;
this.currentExpandedLength = 0;
this.doctypefound = false;
@@ -181,7 +192,7 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode,
}
function resolveNameSpace(tagname) {
- if (this.options.removeNSPrefix) {
+ if (this.removeNSPrefix) {
const tags = tagname.split(':');
const prefix = tagname.charAt(0) === '/' ? '/' : '';
if (tags[0] === 'xmlns') {
@@ -206,82 +217,148 @@ function buildAttributesMap(attrStr, jPath, tagName, force = false) {
const matches = getAllMatches(attrStr, attrsRegx);
const len = matches.length; //don't make it inline
- const attrs = {};
-
- // Pre-process values once: trim + entity replacement
- // Reused in both matcher update and second pass
- const processedVals = new Array(len);
- let hasRawAttrs = false;
- const rawAttrsForMatcher = {};
-
+ const attrNames = new Array(len);
for (let i = 0; i < len; i++) {
- const attrName = this.resolveNameSpace(matches[i][1]);
- const oldVal = matches[i][4];
-
- if (attrName.length && oldVal !== undefined) {
- let val = oldVal;
- if (options.trimValues) val = val.trim();
- val = this.replaceEntitiesValue(val, tagName, this.readonlyMatcher);
- processedVals[i] = val;
-
- rawAttrsForMatcher[attrName] = val;
- hasRawAttrs = true;
- }
+ attrNames[i] = this.resolveNameSpace(matches[i][1]);
}
+ return this.buildAttributes(matches, attrNames, jPath, tagName);
+ }
+}
- // Update matcher ONCE before second pass, if applicable
- if (hasRawAttrs && typeof jPath === 'object' && jPath.updateCurrent) {
- jPath.updateCurrent(rawAttrsForMatcher);
+/**
+ * Build the attributes map of a tag
+ * @param {Array<Array<string>>} matches - attribute matches of attrsRegx; [4] is the raw value
+ * @param {Array<string>} attrNames - name to use for each match ('' to skip it)
+ * @param {Matcher} jPath - matcher positioned at the tag
+ * @param {string} tagName
+ */
+function buildAttributes(matches, attrNames, jPath, tagName) {
+ const options = this.options;
+ const len = matches.length; //don't make it inline
+ const attrs = {};
+
+ // Pre-process values once: trim + entity replacement
+ // Reused in both matcher update and second pass
+ const processedVals = new Array(len);
+ let hasRawAttrs = false;
+ const rawAttrsForMatcher = {};
+
+ for (let i = 0; i < len; i++) {
+ const attrName = attrNames[i];
+ const oldVal = matches[i][4];
+
+ if (attrName.length && oldVal !== undefined) {
+ let val = oldVal;
+ if (options.trimValues) val = val.trim();
+ val = this.replaceEntitiesValue(val, tagName, this.readonlyMatcher);
+ processedVals[i] = val;
+
+ rawAttrsForMatcher[attrName] = val;
+ hasRawAttrs = true;
}
+ }
- // Hoist toString() once — path doesn't change during attribute processing
- const jPathStr = options.jPath ? jPath.toString() : this.readonlyMatcher;
+ // Update matcher ONCE before second pass, if applicable
+ if (hasRawAttrs && typeof jPath === 'object' && jPath.updateCurrent) {
+ jPath.updateCurrent(rawAttrsForMatcher);
+ }
- // Second pass: apply processors, build final attrs
- let hasAttrs = false;
- for (let i = 0; i < len; i++) {
- const attrName = this.resolveNameSpace(matches[i][1]);
+ // Hoist toString() once — path doesn't change during attribute processing
+ const jPathStr = options.jPath ? jPath.toString() : this.readonlyMatcher;
- if (this.ignoreAttributesFn(attrName, jPathStr)) continue;
+ // Second pass: apply processors, build final attrs
+ let hasAttrs = false;
+ for (let i = 0; i < len; i++) {
+ const attrName = attrNames[i];
… 988 more lines not shown (9 files changed in all)
Sample 4 of 8
Inline list editing: column_editable_list and a JSON update endpoint
sqladmin, a Python library that adds an admin web interface for SQLAlchemy models (sync or async engines) to Starlette and FastAPI apps.
Let admin users edit chosen columns directly on the list page. This needs ModelView.column_editable_list, checked when the view is added, and a JSON endpoint POST /{identity}/ajax/update/{pk:path} that validates and saves one field, with editable cells carrying data attributes that point to it, and error checks applied in a fixed order with set status codes (404, 403, 404, 403, then 400).
Why it is hard
add_view()must reject relationships, dotted paths, Python properties and columns absent from list or edit form, even in running event loops, thoughscaffold_formis async.- Five ordered error cases; 400s carry field errors with no save, hooks or audit, or the
on_model_change/DB exception text with the row unchanged. nameis the attribute (data), not the WTForms key (data_); empty input on nullable columns stores NULL, though the nullable-Boolean select turns ‘’ into False.valueanddisplaymust reflect the stored row after saving, with every path behaving the same on sync and async engines.
The request, verbatim (the agent’s only input)
Changing a value on many rows means an edit-page trip per row. Add ModelView.column_editable_list (default []; names or InstrumentedAttributes). Each entry must be a mapped column (no relationship/dotted path) appearing in the list and on the edit form, else Admin.add_view() raises sqladmin.exceptions.ImproperlyConfigured.
Add POST /{identity}/ajax/update/{pk:path} named admin:ajax_update (Admin.ajax_update) with form fields name (attribute name, e.g. data for field data_) and value. Validate only that edit-form field, then save its data via update_model(request, pk, {name: data}); empty input on nullable columns stores NULL. on_model_change/after_model_change get ({name: data}, obj, False, request); audit gets AuditEntry(action="update", identity=..., pk=<str>, changes={name: data}). Return 200 JSON: pk (string), name, value (stored value as JSON; non-str Enum → member name, dates → ISO), display (str() of the list cell). A Response from after_model_change replaces it. Sync and async engines alike.
Editable columns' <td>s in rows showing the edit link, and no others, get data-inline-edit-name="<attribute>" and data-inline-edit-url="<row's absolute ajax_update URL>".
Errors
In order: unknown identity 404; can_edit/is_accessible false 403; no row 404; check_can_edit not True 403; name missing or not editable 400. 400s give non-empty {"errors": [...]}: that field's WTForms messages if invalid (no save, hooks or audit), or [str(exc)] if on_model_change or the DB raises (row unchanged).
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- column_editable_list defaults to "[]" and accepts names, InstrumentedAttributes or a mix, on sync and async engines and inside a running event loop.
- add_view(), not declaration, raises ImproperlyConfigured for relationships, dotted paths, unknown names, Python properties, and columns absent from list or edit form (rules, custom forms, pk/fk).
- The route is named admin:ajax_update, saves the value and returns pk/name/value/display JSON, including for slash-containing and composite primary keys.
- name takes the attribute name (data for field data_); sending the WTForms key data_ gets a 400 with errors on sync and async engines.
- Only the submitted field is validated; an invalid value returns that field's errors and triggers no save, hooks or audit.
- Editable list cells carry data-inline-edit-name and an absolute data-inline-edit-url; the 807 existing tests must still pass, and the package must still build and pass mypy.
- fail-to-pass tests
- 209
- regression tests
- 807
- build/type-check gates
- 2
- files in the reference solution
- 9
- lines it adds
- +474
Key moments of the trajectory
Calls 4–5, 8, 11, 16–17, 19 Created branch feature/column-editable-list, then read application.py, models.py, _queries.py, forms.py and WTForms’ Form source, noting that a data column’s form field is keyed data_ but rendered with HTML name data.
Calls 35–37, 43–44 From sqladmin’s field classes, noticed that the select field for nullable Booleans turns an empty string into False, which could break ‘empty input stores NULL’; then checked how WTForms treats missing keys.
Calls 46–52 Hit the 30-second command limit, so ran the baseline suite in the background (807 passed) and wrote a repro showing the feature missing.
Calls 53–57 Probed SQLAlchemy mapper attributes (computed column_propertys, hybrids, relationships), then added column_editable_list, validated in ModelView.__init__ by a helper that mirrors sqladmin’s form-scaffolding rules to decide edit-form membership.
Calls 58–61 Added a to_json_value helper, an _ajax_update guard that loads the row before calling check_can_edit to follow the 404/403/404/403 order, and the ajax_update endpoint saving through the existing update_model.
Calls 65, 70–73, 77–79 Made list.html compute check_can_edit once per row and tag editable cells, then wrote an inline editor in main.js.
Calls 84–86, 95, 98–100, 110–111 Wrote 69 sync and 15 async tests, closed two gaps found by coverage, then reran the full suite: 908 passed, 4 skipped, 98% coverage, with ruff, mypy and bandit clean.
Calls 114–117 Committed everything on the branch; a first final message without a tool call was rejected, so the agent resent its summary and submitted.
What the agent committed
- model calls
- 117
- bash commands
- 139
- minutes
- 64
- files changed
- 11
- lines added / removed
- +2,062 / −3
On a new branch, the agent committed column_editable_list with add-time validation, the admin:ajax_update endpoint, the list-cell attributes, a JS editor, CSS, docs and its own tests. The endpoint has a guard in the specified error order, validates a single field and saves through the existing update_model.
The agent's committed change (excerpt)
diff --git a/sqladmin/application.py b/sqladmin/application.py
--- a/sqladmin/application.py
+++ b/sqladmin/application.py
@@ -15,10 +15,12 @@ from typing import (
)
from urllib.parse import parse_qsl, urljoin
+import wtforms
from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader, PrefixLoader
from sqlalchemy.engine import Engine
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy.orm.exc import DetachedInstanceError
from starlette import status
from starlette.applications import Starlette
from starlette.datastructures import URL, FormData, MultiDict, UploadFile
@@ -53,7 +55,9 @@ from sqladmin.forms import WTFORMS_ATTRS, WTFORMS_ATTRS_REVERSED
from sqladmin.helpers import (
get_object_identifier,
is_async_session_maker,
+ object_identifier_values,
slugify_action_name,
+ to_json_value,
)
from sqladmin.i18n import (
BABEL_INSTALLED,
@@ -88,6 +92,24 @@ def _save_button_clicked(form: FormData, label: str) -> bool:
return form.get("save") in (label, gettext(label))
+def _ajax_errors(*messages: Any) -> JSONResponse:
+ """The 400 answer of `Admin.ajax_update`."""
+
+ return JSONResponse(
+ {"errors": [str(message) for message in messages]},
+ status_code=status.HTTP_400_BAD_REQUEST,
+ )
+
+
+def _csrf_field(form: wtforms.Form) -> wtforms.Field | None:
+ """The CSRF token field of ``form``, if its form class enables WTForms CSRF."""
+
+ if not getattr(form.meta, "csrf", False):
+ return None
+ name = form.meta.csrf_field_name
+ return form[name] if name in form else None
+
+
class BaseAdmin:
"""Base class for implementing Admin interface.
@@ -458,6 +480,33 @@ class BaseAdminView(BaseAdmin):
if can_edit_row is not True:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+ async def _ajax_update(self, request: Request) -> tuple[ModelView, Any]:
+ """Guard an in-place edit from the list page and return its view and row.
+
+ Unlike `_edit`, the row is loaded before `check_can_edit` is asked
+ about it, so an unknown row is a 404 rather than a question about
+ ``None``.
+ """
+
+ model_view = self._find_model_view(request.path_params["identity"])
+ if not model_view.can_edit or not model_view.is_accessible(request):
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+
+ try:
+ object_identifier_values(request.path_params["pk"], model_view.model)
+ except (ArithmeticError, TypeError, ValueError) as exc:
+ # A malformed identifier matches no row.
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from exc
+
+ obj = await model_view.get_object_for_edit(request)
+ if obj is None:
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
+
+ if await model_view.check_can_edit(request, obj) is not True:
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
+
+ return model_view, obj
+
async def _import(self, request: Request) -> None:
model_view = self._find_model_view(request.path_params["identity"])
if not model_view.is_accessible(request):
@@ -650,6 +699,12 @@ class Admin(BaseAdminView):
Route(
"/{identity}/ajax/lookup", endpoint=self.ajax_lookup, name="ajax_lookup"
),
+ Route(
+ "/{identity}/ajax/update/{pk:path}",
+ endpoint=self.ajax_update,
+ name="ajax_update",
+ methods=["POST"],
+ ),
Route("/login", endpoint=self.login, name="login", methods=["GET", "POST"]),
Route("/logout", endpoint=self.logout, name="logout", methods=["GET"]),
Route(
@@ -1077,6 +1132,106 @@ class Admin(BaseAdminView):
data = [loader.format(m) for m in await loader.get_list(request, term)]
return JSONResponse({"results": data})
+ @login_required
+ async def ajax_update(self, request: Request) -> Response:
+ """Save one column of one row, edited in place on the list page.
+
+ Takes the form fields ``name``, the attribute to change (which must
+ be in `ModelView.column_editable_list`), and ``value``. The value is
+ validated by that column's edit form field alone and saved through
+ `ModelView.update_model`, so `on_model_change`, `after_model_change`
+ and the audit backend all see ``{name: data}``. An empty ``value``
+ stores ``NULL`` in a nullable column.
+
+ Answers with JSON: the row's ``pk``, the ``name``, the stored
+ ``value`` and its ``display``, i.e. the list page cell as a string.
+ Invalid input or a failed save answers 400 with ``{"errors": [...]}``,
+ while a `Response` returned by `after_model_change` is used as is.
+ """
+
+ model_view, _ = await self._ajax_update(request)
+ pk = request.path_params["pk"]
+
+ request_form = await request.form()
+ name = request_form.get("name")
+ if not isinstance(name, str) or not name:
+ return _ajax_errors("The 'name' field is required.")
+ if name not in model_view._editable_fields:
+ return _ajax_errors(f"{name!r} cannot be edited in place.")
+
+ Form = await model_view.scaffold_form(model_view._form_edit_rules)
+ form = Form()
+ key = WTFORMS_ATTRS.get(name, name)
+ if key not in form:
+ return _ajax_errors(f"{name!r} is not a field of the edit form.")
… 2,125 more lines not shown (11 files changed in all)
Sample 5 of 8
Opt-in 429/503 retries and parsed rate-limit headers in go-jira
go-jira, a Go client library for the Jira REST API, split into cloud (Jira Cloud) and onpremise (Jira Server / Data Center) packages.
Add an opt-in RetryPolicy for Client.Do in both cloud and onpremise. It retries 429/503 responses only when the body is replayable and, unless RetryNonIdempotent is set, only for GET, HEAD, OPTIONS, PUT and DELETE, waiting per Retry-After or exponential backoff, both capped at MaxBackoff; a new RateLimit field also exposes parsed rate-limit headers on every returned Response.
Why it is hard
- Identical retry semantics in two packages,
cloudandonpremise, while each gets its ownRateLimitstruct parsed from partly different headers. - Retry eligibility combines four conditions: 429/503 only, GET/HEAD/OPTIONS/PUT/DELETE unless
RetryNonIdempotent, replayable bodies (nil or withGetBody), and at mostMaxRetries+1requests. Backoffedge cases:Retry-After: 0must be honoured, HTTP-dates and negatives ignored, results capped atMaxBackoffwithout overflow, defaults for non-positive bounds.- Error paths are strict: messages and
NewJiraErrorresults unchanged, transport errors returned immediately, and a nil*Responsewith anerrors.Is-matching error whenSleepfails.
The request, verbatim (the agent’s only input)
Every cloud and onpremise service method goes through Client.Do, which makes a single attempt, so integrations hand-roll 429/503 retries and parse rate-limit headers themselves. Let's bring opt-in retries and parsed rate-limit info to both packages, with identical retry semantics.
Both get a RetryPolicy struct (MaxRetries int, MinBackoff, MaxBackoff time.Duration, RetryNonIdempotent bool, Sleep func(ctx context.Context, d time.Duration) error) and a Client.RetryPolicy *RetryPolicy field. NewClient leaves it nil; a nil policy or MaxRetries <= 0 means a single request and today's exact behaviour.
Otherwise Do retries only when the status is 429 or 503, the method is GET, HEAD, OPTIONS, PUT or DELETE (any method if RetryNonIdempotent is true), the body is replayable (req.Body == nil or req.GetBody != nil) and fewer than MaxRetries retries were made, so at most MaxRetries+1 requests go out. No other status is retried (not even 500/502/504); http.Client transport errors return immediately. Before retry n (1-based), drain and close the previous response and call Sleep(req.Context(), policy.Backoff(n, previousResponse)) even for a zero delay; a nil Sleep waits out the delay on a timer but returns ctx.Err() as soon as the context is done. If Sleep returns an error, stop and return a nil *Response with an error matching it under errors.Is. Retries resend the same method, URL, headers and a byte-identical body from req.GetBody; requests from NewRequest, NewMultiPartRequest, and NewRawRequest with a strings.Reader, bytes.Reader or bytes.Buffer are replayable, while NewRawRequest over other readers is sent once. When retries run out or a response isn't retryable, Do handles it exactly as today (decode 2xx into v, else return the *Response and the usual CheckResponse error), so service methods still return their NewJiraError result and resp.StatusCode is the final status.
func (p *RetryPolicy) Backoff(attempt int, resp *http.Response) time.Duration returns resp's Retry-After in seconds when resp is non-nil and the header is a non-negative integer, else MinBackoff * 2^(attempt-1); HTTP-date, negative or malformed values are ignored. Either result is capped at MaxBackoff without overflowing (Backoff(100, nil) == MaxBackoff). MinBackoff <= 0 means 1s, MaxBackoff <= 0 means 30s, attempt < 1 counts as 1. For example, with MaxRetries 3, MinBackoff 100ms and MaxBackoff 250ms, a GET to an always-503 server (no Retry-After) makes 4 requests, Sleep sees 100ms, 200ms, 250ms, and the call fails with resp.StatusCode 503.
Also add a Response.RateLimit RateLimit field, filled from headers on every *Response Do returns (2xx or error status, with or without a policy); missing or unparseable headers leave fields zero. In cloud, RateLimit has Limit, Remaining int (X-RateLimit-Limit, X-RateLimit-Remaining), Reset time.Time (X-RateLimit-Reset, RFC 3339), NearLimit bool (true iff X-RateLimit-NearLimit is "true", ignoring case), Reason string (RateLimit-Reason, verbatim) and RetryAfter time.Duration (Retry-After, non-negative integer seconds). In onpremise it has the same Limit, Remaining and RetryAfter, plus FillRate int (X-RateLimit-FillRate) and Interval time.Duration (X-RateLimit-Interval-Seconds, integer seconds). Nothing else on Response or in error messages should change.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- Both packages expose RetryPolicy and Client.RetryPolicy; NewClient leaves it nil, and a nil or MaxRetries <= 0 policy sends exactly one request.
- Retries only for 429/503, allowed methods and replayable bodies, capped at MaxRetries+1 requests; 500/502/504, transport errors and redirect loops are not retried.
- Before retry n the previous response is drained and closed, and Sleep receives req.Context() and Backoff(n, prev), even for a zero delay.
- A nil Sleep waits on a timer yet returns ctx.Err() once the context ends; a failing Sleep yields a nil *Response and an errors.Is-matching error.
- Retries resend an identical request with the GetBody body; issue update/delete calls retry, while issue create and attachment uploads retry only with RetryNonIdempotent.
- The request's always‑503 example ends after 4 requests with status 503 and the usual NewJiraError; Response.RateLimit and unchanged error messages are checked end to end.
- fail-to-pass tests
- 495
- regression tests
- 389
- build/type-check gate
- 1
- files in the reference solution
- 8
- lines it adds
- +575
Key moments of the trajectory
Calls 3, 5–6, 10 Read both jira.go files, diffed cloud against onpremise (including error.go), counted 198 .Do( call sites outside tests and examples, and ran a green baseline of build, tests and go vet.
Calls 12–14 Read net/http source to confirm GetBody is set automatically for bytes.Buffer, bytes.Reader and strings.Reader, and found http.Client adds cookie-jar cookies to req.Header, so it chose to snapshot headers before the first send.
Calls 14–16 Created feature/retry-policy-and-rate-limits from main and wrote a standalone reproduction; after the checksum-database lookup failed for lack of network access, it reused the repo’s go.sum, and compilation failed because the requested API did not exist yet.
Calls 18–22 Wrote per-package retry.go and ratelimit.go (onpremise/retry.go copied from cloud/retry.go with only the package line changed), and patched both jira.go files so Do calls a new send loop and newResponse fills RateLimit.
Calls 23, 25, 29 The reproduction then passed in both packages (4 requests, Sleep received 100ms, 200ms and 250ms, final 503); the agent wrote package-agnostic retry tests and copied them to onpremise with only the package clause changed.
Calls 31–35, 37–38 Mutation-tested its own tests: after a 30-second command timeout it restored retry.go and reran in smaller batches, and redid two mutations whose ‘kills’ were vet/compile errors; every mutation was then caught by a failing test.
Calls 41–42, 44, 49 Re-ran staticcheck after noticing $? came from tail, confirmed a 4-request retry sequence reused one connection, compile-checked the README example, and ran the new tests 5 times under -race.
Calls 53–57 Committed all 12 files in one commit, checked the merge-base equals main and the tree is clean, re-ran -race tests, gofmt and vet; two tool-less summary replies drew format errors before submission.
What the agent committed
- model calls
- 57
- bash commands
- 62
- minutes
- 31
- files changed
- 12
- lines added / removed
- +2,533 / −6
The agent committed 12 files on a new branch from main: per-package retry.go and ratelimit.go with identical retry code apart from the package line, Client.Do routed through a send loop, RateLimit parsed in newResponse, plus tests, README and CHANGELOG updates.
The agent's committed change (excerpt)
diff --git a/cloud/retry.go b/cloud/retry.go
new file mode 100644
--- /dev/null
+++ b/cloud/retry.go
@@ -0,0 +1,262 @@
+package cloud
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "math"
+ "net/http"
+ "strconv"
+ "time"
+)
+
+const (
+ // defaultMinBackoff is used by RetryPolicy.Backoff if MinBackoff is not positive.
+ defaultMinBackoff = 1 * time.Second
+
+ // defaultMaxBackoff is used by RetryPolicy.Backoff if MaxBackoff is not positive.
+ defaultMaxBackoff = 30 * time.Second
+
+ // maxDrainBytes limits how much of a response body is read before a retry discards it.
+ // Small bodies are read completely so that the connection can be reused,
+ // without the risk of reading an arbitrarily large body.
+ maxDrainBytes = 64 << 10
+
+ // maxDuration is the largest representable time.Duration.
+ maxDuration = time.Duration(math.MaxInt64)
+
+ // maxDurationSeconds is the largest number of whole seconds a time.Duration can hold.
+ maxDurationSeconds = int64(maxDuration / time.Second)
+)
+
+// RetryPolicy configures how Client.Do retries requests that Jira rejected because of
+// rate limiting (429 Too Many Requests) or temporary unavailability (503 Service Unavailable).
+//
+// Retries are opt-in: they only happen if Client.RetryPolicy is set and MaxRetries is positive.
+// A request is retried only if all of the following conditions are met:
+//
+// - the response status is 429 or 503. No other status is retried (not even 500, 502 or 504),
+// and errors returned by the http.Client (e.g. network errors) are returned immediately.
+// - the method is GET, HEAD, OPTIONS, PUT or DELETE, or RetryNonIdempotent is set.
+// - the request body can be replayed: the request has no body or Request.GetBody is set.
+// This is the case for all requests created by NewRequest and NewMultiPartRequest,
+// and for NewRawRequest with a *strings.Reader, *bytes.Reader or *bytes.Buffer body.
+// Requests created by NewRawRequest with any other reader are sent only once.
+// - fewer than MaxRetries retries have been made, so at most MaxRetries+1 requests are sent.
+//
+// A retry resends the same method, URL, headers and body.
+// Before retry n (1-based), the previous response is drained and closed and
+// Sleep is called with the request's context and Backoff(n, previousResponse).
+//
+// Once a response is not retried (or no retries are left), Do handles it as without a policy.
+// The returned Response therefore always describes the last attempt.
+type RetryPolicy struct {
+ // MaxRetries is the maximum number of retries per request.
+ // Zero or a negative value disables retries.
+ MaxRetries int
+
+ // MinBackoff is the base delay of the exponential backoff (default 1s if <= 0).
+ MinBackoff time.Duration
+
+ // MaxBackoff caps every delay, including those requested via Retry-After (default 30s if <= 0).
+ MaxBackoff time.Duration
+
+ // RetryNonIdempotent enables retries for all request methods, including POST and PATCH.
+ // Only enable it if sending such a request more than once is safe for your use case.
+ RetryNonIdempotent bool
+
+ // Sleep waits for the given delay before a retry.
+ // It is called before every retry, even if the delay is zero.
+ // If it returns an error, Do stops retrying and returns a nil *Response
+ // together with an error that wraps the returned error.
+ //
+ // If Sleep is nil, Do waits for the delay on a timer, but returns
+ // ctx.Err() as soon as the context is done.
+ Sleep func(ctx context.Context, d time.Duration) error
+}
+
+// Backoff returns how long to wait before retry number attempt (1-based),
+// given resp, the response to the previous attempt (which may be nil).
+//
+// If resp has a Retry-After header holding a non-negative integer number of seconds,
+// that delay is returned. Otherwise, the delay grows exponentially: MinBackoff * 2^(attempt-1).
+// Retry-After values in the HTTP-date format as well as negative or malformed values are ignored.
+// The result is capped at MaxBackoff.
+//
+// A MinBackoff <= 0 is treated as 1s, a MaxBackoff <= 0 as 30s and an attempt < 1 as 1.
+func (p *RetryPolicy) Backoff(attempt int, resp *http.Response) time.Duration {
+ minBackoff, maxBackoff := defaultMinBackoff, defaultMaxBackoff
+ if p != nil && p.MinBackoff > 0 {
+ minBackoff = p.MinBackoff
+ }
+ if p != nil && p.MaxBackoff > 0 {
+ maxBackoff = p.MaxBackoff
+ }
+
+ if resp != nil {
+ if d, ok := parseDelaySeconds(resp.Header.Get("Retry-After")); ok {
+ if d > maxBackoff {
+ return maxBackoff
+ }
+ return d
+ }
+ }
+
+ if attempt < 1 {
+ attempt = 1
+ }
+ // minBackoff << shift <= maxBackoff holds exactly if minBackoff <= maxBackoff >> shift.
+ // Checking it this way avoids overflowing for large attempts.
+ shift := uint(attempt - 1)
+ if minBackoff > maxBackoff>>shift {
+ return maxBackoff
+ }
+ return minBackoff << shift
+}
+
+// shouldRetry reports whether p allows retrying req at all.
+// It does not consider the response status or the number of retries made so far.
+func (p *RetryPolicy) shouldRetry(req *http.Request) bool {
+ if p == nil || p.MaxRetries <= 0 {
+ return false
+ }
+ if !p.RetryNonIdempotent && !isIdempotentMethod(req.Method) {
+ return false
+ }
… 2,538 more lines not shown (12 files changed in all)
Sample 6 of 8
Stateless cron occurrence enumeration and task previous-run lookups
node-cron (TypeScript): a cron-expression job scheduler for Node.js that runs tasks inline or in forked background processes, published as ESM and CJS builds.
Add stateless getNextRuns and getPreviousRuns that list a cron expression’s fire times after or before any instant without creating a task, plus getPreviousRun() / getPreviousRuns(count) on scheduled tasks. Results must match exactly what the scheduler fires, across all cron syntax and DST transitions.
Why it is hard
- Backward results must equal the existing forward search’s output reversed, so the backward search must mirror its actual DST behavior, not textbook wall-clock rules.
- All syntax (
L,L-n,nW,LW,nL,#, names, nicknames, wrap-around ranges) must work in reverse within a 36,600-day bound per search. - One contract spans named and default exports,
NodeCrontypings, ESM and CJS builds, and inline and background tasks, which must agree in every status. - Subtleties: millisecond
from, strict exclusion offrom, short arrays instead of throws, per-argument error types, and task lookups from the schedule, not run history.
The request, verbatim (the agent’s only input)
Today you can only preview a cron expression by creating a task, and only forward from now. Nothing tells you when a schedule last fired, which people need for catch-up after downtime, dashboards and tooling. Can you pick up stateless occurrence enumeration from any instant in either direction, plus previous-run lookups on tasks?
Add module-level getNextRuns(expression, options?): Date[] and getPreviousRuns(expression, options?): Date[], with options { count?: number; from?: Date | string | number; timezone?: string }, as named exports of the package entry and members of the default cron export and NodeCron interface, in the ESM and CJS builds. getNextRuns returns the first count occurrences strictly after from, ascending; getPreviousRuns returns the count latest occurrences strictly before from, most recent first. Defaults: count 1, from now, timezone the process's local zone; count 0 returns []. Occurrences are whole seconds but from may carry milliseconds: for */20 * * * * * from 12:00:00.500Z, previous 2 is [12:00:00Z, 11:59:40Z] and next is [12:00:20Z].
Both directions must reproduce exactly what the scheduler fires: backward results are the forward schedule reversed, with no forward occurrence between two returned instants skipped. That holds for all syntax (nicknames, 5 and 6 fields, names, ?, L, L-n, nW, LW, nL, # and wrap-around ranges) and for DST: in a fall-back overlap a wall-clock time fires once, at its first (pre-transition) instant, so the repeated hour never fires, and times skipped by spring-forward never fire. In America/New_York, * * * * * previous 2 from 2025-11-02T06:20:00Z must give [05:59Z, 05:58Z], not 06:19Z, and 30 2 * * * previous 2 from 2025-03-10T12:00:00Z gives [2025-03-10T06:30Z, 2025-03-08T07:30Z].
Each successive search spans 36,600 days from the last occurrence found (or from from); if a search finds nothing, return the shorter array (possibly empty) instead of throwing. So 0 0 0 31 * 0#1 gives [], while 0 0 0 29 2 1 previous 5 from 2026-01-01 in UTC gives Feb 29 of 2016, 1988, 1960, 1932 and 1904.
Bad arguments still throw: an invalid expression throws an Error whose message is exactly the one cron.parse() throws for it; a count that isn't a non-negative integer throws a RangeError; a from that isn't a Date, string or number giving a valid date via new Date(from) throws a TypeError; an unknown timezone throws a RangeError.
On the ScheduledTask interface, add getPreviousRun(): Date | null and getPreviousRuns(count: number): Date[], implemented by inline and background tasks with identical results (background tasks answer from the parent process alone). getPreviousRun() returns the latest occurrence strictly before now for the task's expression and timezone, or null if none, regardless of status (including stopped and destroyed). getPreviousRuns(count) returns the count latest such occurrences, most recent first, and [] for count <= 0.
Leave task firing, the DST model, the existing task methods (getNextRun(), getNextRuns(count), match(), msToNext(), including their 'Could not find next matching date' throw) and cron.parse, validate and validateDetailed unchanged.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- Named and default-export members, plus task getPreviousRun()/getPreviousRuns(), in source and both builds, declared on NodeCron/ScheduledTask and in node-cron.d.ts/.d.cts; enumeration creates or registers no tasks.
- An occurrence at from is excluded both ways but appears when from shifts 1 ms; next runs ascend, previous runs come most recent first.
- Every syntax form (nicknames, 5/6 fields, names, "?", L, L-n, nW, LW, nL, "#", wrap-around ranges) enumerates both ways while validate()/validateDetailed() still accept it.
- The request's stated examples, DST gap and overlap, timezone and defaults, from as Date/ISO/epoch-ms, the 36,600-day span, "[]" for never-matching expressions, and error types.
- Inline and background getPreviousRun()/getPreviousRuns() agree in every status, daemon-free; existing task methods, including the 'Could not find next matching date' throw, and task firing unchanged.
- 339 tests must go from failing to passing, and the 753 existing tests plus the build and typecheck checks (755 in all) must stay green.
- fail-to-pass tests
- 339
- regression tests
- 753
- build/type-check gates
- 2
- files in the reference solution
- 18
- lines it adds
- +1,399
Key moments of the trajectory
Calls 3–5, 15, 20–22 Read the entry point, both task classes, MatcherWalker and localized-time.ts; ran the suite in the background to get around a 30-second command limit (753 tests passed); tsc and eslint were clean.
Calls 26–27, 30–31 Probing with a TypeScript loader hook, the agent found the forward walker maps east-of-UTC overlaps to the later instant and skips fires after Lord Howe’s half-hour spring-forward; the backward search would mirror both.
Calls 34, 36–37, 39–40 Kept matchNext()'s throw while moving its core into a non-throwing findNext(), added a mirrored findPrevious(), wrote occurrences.ts (gap-jump correction) and runs.ts (validation), and wired exports and task methods.
Calls 41, 49–51 Every request example passed, as did a gap-targeted fuzz and its uncorrected control; concluding the harness never reached the jump, the agent probed Lord Howe directly and retargeted the fuzz: control 21/300 failures, corrected 0.
Calls 70–71 Mutation-tested the new tests by disabling the gap and overlap corrections; the general fuzz test missed the overlap mutant, so the agent sampled closer to DST transitions until that test caught it too.
Calls 87, 97–99 Checked the refactor against a clean main worktree: forward-walk timings were within noise, and 2,500 random chains of up to four calls each (10 zones) gave identical results and throws.
Calls 100–104, 108 Built dist/ and imported it through the package’s exports map as ESM and CJS consumers, typechecked consumer code against both declaration files, and ran a real forked background task end to end.
Calls 117–120, 124 Rebuilt dist/ after a final refactor, removed the temporary worktree, committed on feat/previous-runs-enumeration, then re-ran the full suite on the committed code: 42 files, 867 tests passing.
What the agent committed
- model calls
- 125
- bash commands
- 145
- minutes
- 93
- files changed
- 15
- lines added / removed
- +1,386 / −12
The agent added src/runs.ts (both module-level functions, with argument validation), a backward findPrevious() mirroring the forward walk, a gap-jump correction in src/time/occurrences.ts, and schedule-based getPreviousRun() / getPreviousRuns() on inline and background tasks, plus README docs and tests, all committed on a new branch.
The agent's committed change (excerpt)
diff --git a/src/time/matcher-walker.ts b/src/time/matcher-walker.ts
--- a/src/time/matcher-walker.ts
+++ b/src/time/matcher-walker.ts
@@ -1,4 +1,4 @@
-import { LocalizedTime, localTimeToTimestamp } from './localized-time';
+import { LocalizedTime, WallClock, getOffsetMinutes, localTimeToTimestamp } from './localized-time';
import { TimeMatcher } from './time-matcher';
import { matchesDayOfMonth, DayOfMonthField } from './day-of-month';
import { matchesDayOfWeek, DayOfWeekField } from './day-of-week';
@@ -8,6 +8,13 @@ import { matchesDayOfWeek, DayOfWeekField } from './day-of-week';
// cheap, so reaching it means the expression can never match (e.g. `0 0 31 2 *`).
const MAX_DAYS = 366 * 100;
+const DAY_MS = 86400000;
+
+// A JS Date spans +/-8.64e15 ms. Within a day of either end, the UTC offset
+// applied while materializing a wall-clock time can step outside that range.
+const DATE_RANGE_MS = 8.64e15;
+const SAFE_RANGE_MS = DATE_RANGE_MS - DAY_MS;
+
export class MatcherWalker {
baseDate: Date;
timeMatcher: TimeMatcher;
@@ -40,7 +47,21 @@ export class MatcherWalker {
/**
* Finds the next instant that satisfies the expression, strictly after the
- * base date.
+ * base date. Throws when there is none within the search bound; see
+ * findNext() for the algorithm.
+ */
+ matchNext(): LocalizedTime {
+ const next = this.findNext();
+ if (next === null) {
+ throw new Error('Could not find next matching date within reasonable time range');
+ }
+ return new LocalizedTime(new Date(next), this.timezone);
+ }
+
+ /**
+ * Finds the next instant that satisfies the expression, strictly after the
+ * base date, and returns its timestamp, or null when there is none within
+ * the search bound (MAX_DAYS calendar days, starting at the base day).
*
* Instead of scanning time tick by tick, it infers candidates directly from
* the cron fields: it walks the calendar day by day (a Y/M/D is the same
@@ -50,10 +71,7 @@ export class MatcherWalker {
* real matcher, which both enforces the weekday constraint and rejects
* non-existent local times (the DST spring-forward gap) by construction.
*/
- matchNext(): LocalizedTime {
- const months = this.months;
- const days = this.days;
-
+ findNext(): number | null {
const baseMs = Math.floor(this.baseDate.getTime() / 1000) * 1000;
const baseParts = new LocalizedTime(new Date(baseMs), this.timezone).getParts();
@@ -65,20 +83,82 @@ export class MatcherWalker {
// exactly, so it only skips days that would be rejected anyway, and it
// avoids scanning every time on a day whose weekday can't match (e.g.
// `* * * 15 * 1`, the 15th only when it is a Monday).
- if (months.includes(month) && matchesDayOfMonth(days, year, month, day) && this.matchesWeekday(year, month, day)) {
+ if (this.isCandidateDay(year, month, day)) {
// On the base day the result must be strictly after the base instant;
// on later days any matching time of day qualifies.
const lowerBound = i === 0 ? baseParts : null;
const found = this.firstTimeOnDay(year, month, day, lowerBound, baseMs);
if (found !== null) {
- return new LocalizedTime(new Date(found), this.timezone);
+ return found;
}
}
({ year, month, day } = nextDay(year, month, day));
}
- throw new Error('Could not find next matching date within reasonable time range');
+ return null;
+ }
+
+ /**
+ * Finds the latest instant strictly before the base date that the forward
+ * walk (findNext) can produce, and returns its timestamp, or null when there
+ * is none within the search bound (MAX_DAYS calendar days, ending at the
+ * starting day).
+ *
+ * It mirrors findNext(): it walks the calendar backwards day by day and, on
+ * each eligible day, tries the matching times in descending order. Every
+ * candidate is materialized exactly like the forward walk does, so:
+ * - a wall-clock time repeated by a DST fall-back resolves to the single
+ * instant the scheduler fires for it, never to the other one;
+ * - a time inside a spring-forward gap materializes past the gap and is
+ * rejected by the matcher (it never fires either).
+ * Within a day, later wall-clock times materialize to later instants, so the
+ * first confirmed candidate is the latest one.
+ *
+ * Occurrences are whole seconds while the base may carry milliseconds: the
+ * latest admissible instant is the last whole second strictly before it.
+ */
+ findPrevious(): number | null {
+ let latest = Math.ceil(this.baseDate.getTime() / 1000) * 1000 - 1000;
+ if (!(latest >= -DATE_RANGE_MS)) return null;
+
+ let bound = this.wallClockAt(latest);
+ const materialized = materialize(bound, this.timezone);
+ // NaN: the wall-clock time lies past the end of the Date range; the scan
+ // below simply skips the times that cannot be materialized.
+ if (materialized !== latest && !Number.isNaN(materialized)) {
+ // `latest` is the instance of a repeated (fall-back) wall-clock time that
+ // is never fired, and so is every instant from the start of that run up
+ // to it. Restart from the last instant before the run, whose wall-clock
+ // is an exact bound for the time-of-day scan below. The run is at most as
+ // long as the distance between the two instances of the wall-clock time.
+ const before = this.lastMaterializedBefore(latest, Math.abs(materialized - latest));
+ // Null only for timezone data not shaped like a DST overlap; the scan
+ // below then still never returns an instant after `latest`.
+ /* v8 ignore else */
+ if (before !== null) {
+ latest = before;
+ bound = this.wallClockAt(latest);
+ }
+ }
+
+ let { year, month, day } = bound;
+
+ for (let i = 0; i < MAX_DAYS; i++) {
+ if (this.isCandidateDay(year, month, day)) {
+ // On the starting day only times up to the bound qualify; on earlier
+ // days any matching time of day does.
… 1,495 more lines not shown (15 files changed in all)
Sample 7 of 8
Per-policy authorization explanations for Cedar's API, CLI and FFI
Cedar, a Rust workspace implementing the Cedar authorization policy language: parser, evaluator, authorizer, validator, formatter, CLI and JSON/wasm FFI.
For every policy, report whether each scope constraint matched, which when / unless clause (if any) stopped it or errored, and whether it determined the decision. Expose this via Authorizer::explain, cedar authorize --explain and JSON/wasm FFI functions, with existing behaviour unchanged.
Why it is hard
- The core AST folds all
when/unlessclauses into one expression, even constant-folding literals, so clause indices can’t be read off the AST. - Spans the Rust API, CLI, JSON FFI and
cedar-wasm, while existingis_authorizedresults, CLI output bytes and exit codes must not change. - Each scope constraint is checked separately with template slot values across
==,in,is,is ... inand action lists; any mismatch must skip conditions. - Clause evaluation stops at the first failing clause, with
unlessinverted and both runtime errors and non-boolean results reported asError.
The request, verbatim (the agent’s only input)
I can't see why a request was denied: is_authorized gives only the decision, determining policy IDs and errors, so a policy whose scope never matched looks like one whose when clause failed. I'd like per-policy explanations in the Rust API, CLI and JSON/wasm FFI; existing behaviour must not change.
Add Authorizer::explain(&self, &Request, &PolicySet, &Entities) -> AuthorizationExplanation plus cedar_policy root types AuthorizationExplanation with response() -> &Response, decision() -> Decision, policies() -> impl Iterator<Item = &PolicyExplanation> and policy(&PolicyId) -> Option<&PolicyExplanation>; PolicyExplanation with id() -> &PolicyId, effect() -> Effect, outcome() -> PolicyOutcome, condition_index() -> Option<usize> and bool getters principal_matched(), action_matched(), resource_matched(), is_determining(); and PolicyOutcome (deriving Debug, Clone, Copy, PartialEq, Eq) with NotApplicable, ConditionFalse, Satisfied, Error.
response()matchesis_authorizedon the same inputs (decision, reasons, errors);decision()is its decision.policies()lists all static and template-linked policies (not unlinked templates), sorted by ID string.- Each scope constraint is evaluated on its own (template links use their slot values); all three flags are always computed. If any is false the outcome is
NotApplicableand conditions aren't evaluated, even erroring ones. - Otherwise
when/unlessclauses are evaluated in source order (the order ofconditionsinPolicy::to_json()) until one doesn't pass (whenpasses ontrue,unlessonfalse):ConditionFalseif it evaluated without error,Errorif it errored or wasn't boolean. If all pass, or there are none, it'sSatisfied. condition_index()is the stopping clause's 0-based index forConditionFalse/Error, elseNone.is_determining()is true iff the ID is inresponse().diagnostics().reason().
cedar authorize --explain prints the normal output unchanged, then a blank line, explanation:, and one line per policy in ID order, <id> (<permit|forbid>): <text>, with <text> one of satisfied (determining), satisfied, scope did not match: <parts>, condition <i> not satisfied, error in condition <i>; <parts> joins the non-matching ones of principal, action, resource (in that order) with , , and <i> is condition_index(). Exit codes are unchanged; without the flag output is byte-identical.
In cedar_policy::ffi, add explain_authorization(AuthorizationCall) -> ExplanationAnswer, explain_authorization_json(serde_json::Value) -> Result<serde_json::Value, serde_json::Error> and explain_authorization_json_str(&str) -> Result<String, serde_json::Error>, plus a wasm_bindgen export explainAuthorization (feature wasm) re-exported by cedar-wasm. Inputs that don't parse give the same failure answer as is_authorized. Success is {"type":"success","response":<as isAuthorized>,"policies":[...],"warnings":[...]}; each policy object, in policies() order, has exactly id, effect ("permit"/"forbid"), principalMatched, actionMatched, resourceMatched, outcome ("notApplicable"/"conditionFalse"/"satisfied"/"error"), conditionIndex (number or null) and determining. The _json variants return Err only if the input isn't an AuthorizationCall.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- API surface: Authorizer::explain, the root types and getters, PolicyOutcome derives, policy() lookup by ID, and explanations outliving the policy set they came from.
- response() matches is_authorized for allow-with-errors, deny-by-forbid, default deny and a skipped erroring forbid; is_authorized results themselves are unchanged.
- policies() sorted by ID string, case-sensitively and for default policyN IDs; unlinked templates excluded; empty sets list nothing; links evaluated with their slot values.
- Scope flags for "==", in via hierarchy, is, is ... in and action lists, singly and combined; mismatches yield NotApplicable without evaluating conditions.
- Clause outcomes: when/unless truth rules, stopping at the first failing clause, errors from missing attributes and overflow, non-boolean values, and to_json() order for JSON policies.
- FFI and CLI are checked too: exact FFI keys, failures matching is_authorized, scope mismatches skipping conditions, exact --explain lines, byte-identical no-flag output, unchanged exit codes.
- fail-to-pass tests
- 138
- regression tests
- 3,482
- build/type-check gate
- 1
- files in the reference solution
- 10
- lines it adds
- +1,527
Key moments of the trajectory
Calls 11, 19, 58 Found that the core AST folds all when / unless clauses into one expression, even constant-folding literal clauses, so chose to read clauses from the lossless EST that Policy::to_json() serializes.
Calls 72, 75–76 Added a public est::Policy::conditions() accessor in core, then wrote api/explain.rs and Authorizer::explain.
Calls 78–79, 84 Added the FFI functions, the explainAuthorization wasm export with its cedar-wasm re-export, and the CLI --explain flag.
Calls 89, 91–92 Ran the CLI on hand-written scenarios: cmp confirmed no-flag output is a byte-identical prefix of --explain output, and when { true } when { false } reported condition 1 in Cedar and JSON policies.
Calls 102, 105, 107 Wrote API tests including an oracle test over generated policies; first-run failures were only error source-location differences between text- and JSON-built policy sets, fixed by comparing decisions, reasons and per-policy explanations instead of whole responses.
Calls 116, 118, 121 The full cedar-policy suite passed, including the new Authorizer::explain doc example.
Calls 127, 142–143 Cleared clippy findings in the new code, moving the three scope flags into a small struct for struct_excessive_bools; clippy on cedar-policy then reported zero findings with wasm,partial-eval and with experimental features.
Calls 154, 188–189 Added CHANGELOG entries for cedar-policy, the CLI and cedar-wasm, committed all work as one commit on the new authorization-explanation branch, then confirmed a clean tree and reran the CLI demo.
What the agent committed
- model calls
- 190
- bash commands
- 197
- minutes
- 65
- files changed
- 13
- lines added / removed
- +2,409 / −9
The agent delivered three pieces: Authorizer::explain with AuthorizationExplanation, PolicyExplanation and PolicyOutcome; FFI functions plus a wasm export that reuse is_authorized’s input parsing; and a CLI --explain flag that appends its explanation to the normal output. Clauses are read and evaluated one at a time from the lossless EST.
The agent's committed change (excerpt)
diff --git a/cedar-policy/src/api/explain.rs b/cedar-policy/src/api/explain.rs
new file mode 100644
--- /dev/null
+++ b/cedar-policy/src/api/explain.rs
@@ -0,0 +1,341 @@
…
+fn explain_conditions(
+ evaluator: &Evaluator<'_>,
+ policy: &Policy,
+) -> (PolicyOutcome, Option<usize>) {
+ // The AST has no non-scope constraints exactly when the policy has no
+ // `when` or `unless` clauses, in which case there's nothing to evaluate
+ if policy.ast.non_scope_constraints().is_none() {
+ return (PolicyOutcome::Satisfied, None);
+ }
+ // The AST combines all the clauses of a policy into a single expression
+ // (and may simplify that expression), so we can't recover the individual
+ // clauses from it. Instead, use the same lossless representation which
+ // `Policy::to_json()` uses, so that clause indices match the order of the
+ // `conditions` in the policy's JSON form. If that isn't available, fall
+ // back to the representation derived from the AST, which (like the JSON
+ // form of a policy constructed from an AST) has a single `when` clause
+ // containing all the conditions.
+ let est = policy
+ .lossless
+ .est(|| policy.ast.clone().into())
+ .unwrap_or_else(|_| policy.ast.clone().into());
+ for (index, clause) in est.conditions().iter().enumerate() {
+ // `when` clauses are satisfied when they evaluate to `true`, and
+ // `unless` clauses when they evaluate to `false`
+ let (body, satisfied_when) = match clause {
+ est::Clause::When(body) => (body, true),
+ est::Clause::Unless(body) => (body, false),
+ };
+ // A clause which can't be converted to an AST can't be evaluated
+ // successfully, so we report it as erroring
+ let value = body
+ .clone()
+ .try_into_ast(policy.ast.id())
+ .ok()
+ .and_then(|body| evaluate_to_bool(evaluator, &body, policy.ast.env()));
+ match value {
+ Some(value) if value == satisfied_when => {}
+ Some(_) => return (PolicyOutcome::ConditionFalse, Some(index)),
+ None => return (PolicyOutcome::Error, Some(index)),
+ }
+ }
+ (PolicyOutcome::Satisfied, None)
+}
+
+/// Returns `true` if `expr` evaluates to `true`.
+fn evaluates_to_true(evaluator: &Evaluator<'_>, expr: &ast::Expr, slots: &ast::SlotEnv) -> bool {
+ evaluate_to_bool(evaluator, expr, slots) == Some(true)
+}
+
+/// Evaluate `expr`, returning `None` if evaluation produces an error or a
+/// value which is not a boolean (including a residual, if the request or
+/// entities contain unknowns).
+fn evaluate_to_bool(
+ evaluator: &Evaluator<'_>,
+ expr: &ast::Expr,
+ slots: &ast::SlotEnv,
+) -> Option<bool> {
+ let value = evaluator.interpret(expr, slots).ok()?;
+ match value.value_kind() {
+ ast::ValueKind::Lit(ast::Literal::Bool(b)) => Some(*b),
+ _ => None,
+ }
+}
+
+/// An authorization [`Response`], together with an explanation of how each
+/// policy evaluated against the request.
+///
+/// Returned by [`Authorizer::explain()`](super::Authorizer::explain).
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct AuthorizationExplanation {
…
+impl ScopeMatches {
+ /// Evaluate each of the scope constraints of `policy` on its own, so that
+ /// we can report all of the constraints which don't match
+ fn new(evaluator: &Evaluator<'_>, policy: &Policy) -> Self {
+ // The constraints of the template may contain slots, which are filled
+ // in by `slots` for template-linked policies
+ let template = policy.ast.template();
+ let slots = policy.ast.env();
+ Self {
+ principal: evaluates_to_true(
+ evaluator,
+ &template.principal_constraint().as_expr(),
+ slots,
+ ),
+ action: evaluates_to_true(evaluator, &template.action_constraint().as_expr(), slots),
+ resource: evaluates_to_true(
+ evaluator,
+ &template.resource_constraint().as_expr(),
+ slots,
+ ),
+ }
+ }
+
+ /// Whether all the scope constraints match the request
+ fn all(self) -> bool {
+ self.principal && self.action && self.resource
+ }
+}
+
+/// Explanation of how a single static or template-linked policy evaluated
+/// against an authorization request.
+///
+/// Obtained from [`AuthorizationExplanation::policies()`] or
+/// [`AuthorizationExplanation::policy()`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct PolicyExplanation {
+ /// ID of the policy
+ id: PolicyId,
+ /// Effect of the policy
+ effect: Effect,
+ /// Which of the policy's scope constraints match the request
+ scope: ScopeMatches,
… 2,505 more lines not shown (13 files changed in all)
Sample 8 of 8
RFC 9535 JSONPath selectors for Extract, Unmarshal and Get
go-json, a Go library for encoding and decoding JSON, with a JSONPath API (CreatePath, Path.Extract, Path.Unmarshal, Path.Get).
Extend CreatePath from four selector forms to RFC 9535 selectors (quoted names, wildcards, negative indexes, slices, unions, descendant segments, and filters without function calls or $), with one nodelist semantics shared by Extract, Unmarshal and Get. Paths must also become immutable and safe to share across goroutines, and invalid paths must return *PathError without panicking.
Why it is hard
- One nodelist semantics must hold over raw JSON text (
Extract,Unmarshal) and reflected Go values (Get), including map key order and encoding/json struct-field rules. - Filters need a precedence-aware expression parser (
||,&&,!, §2.3.5.2.2 comparisons, existence tests); any malformed path must yield*PathError, never a panic. - Selector names and raw-JSON member keys can both contain escapes; matching must compare decoded names on both sides, never a key’s still-escaped bytes.
- The old evaluator mutated the shared
Pathmid-call; the rewrite must keep paths unchanged after failures and race-free across goroutines.
The request, verbatim (the agent’s only input)
CreatePath only accepts .name, [n], [*] and ..name, and Path's Extract, Unmarshal and Get misbehave even there. Please implement RFC 9535 selectors, uniformly for all three methods.
Paths are $ followed by segments .name, .*, ."name", [sel, ...], ..name, ..* or ..[sel, ...]. Selectors are quoted names ('a', "a"; JSON string escapes, plus \' in single quotes), *, indexes (optional -, digits), slices start:end:step (parts optional) and filters ?expr. Whitespace may surround bracket selectors, commas and filter tokens. Filters use || (lowest), &&, !, parentheses and comparisons (==, !=, <, <=, >, >=) whose operands are literals (numbers, quoted strings, true, false, null) or singular queries (@ plus any name or index segments); a bare query like @.isbn tests existence, true even for a null value. Any other path (e.g. [1:2:3:4], a function call or $ in a filter) makes CreatePath return a *PathError; it must never panic.
Evaluation follows RFC 9535 (comparisons per §2.3.5.2.2), and:
- a bracket's selectors apply in order, keeping duplicates;
- object members are visited in document order (raw JSON), ascending key order (Go maps) or field order (structs); a name selects all members with that name;
- a descendant segment applies to the node itself, then to its descendants in pre-order (
$..bon{"x":{"b":1},"b":2}gives 2, 1); - a selector that doesn't apply (missing member, out-of-range index, wrong type, scalar, null) selects nothing, without error.
Extract returns the selected values in order, each as its input text minus surrounding whitespace (zero results when nothing matches), and a *SyntaxError for invalid input anywhere, whatever the path; $ returns the input unchanged. Unmarshal decodes the same values in order, as today. Get(src, dst) walks string-keyed maps, structs (fields encoding/json encodes, by JSON key, ignoring omitempty), slices and arrays, following pointers and interfaces; nil values are null, and it never panics. For a singular path (each segment one name or index, no descendants) it stores the value, or errors when nothing matched; otherwise the list (*[]T as today, *any a possibly empty []any); $ stores src.
A Path must never change, even when a call fails, and be safe to share between goroutines; PathString returns exactly the CreatePath argument. RootSelectorOnly stays true only for $, and UsedSingleQuotePathSelector/UsedDoubleQuotePathSelector still report the quotes used by quoted names.
IMPORTANT: Please work on this in a new branch from main and commit everything when you are done.
What the hidden tests check
- CreatePath accepts every listed segment and selector form, returns *PathError for anything else, and never panics on tricky or deeply nested paths.
- Every selector kind and filter feature (comparisons, numbers, strings, structured equality, existence, logic) is checked through Extract, Unmarshal and Get alike.
- Extract returns trimmed input text, with members in document order and duplicate names all selected; "$" returns the input unchanged, and invalid input gives *SyntaxError.
- Get walks structs by JSON key, maps in key order, slices, arrays, pointers, interfaces; stores a value for singular paths, else a list; never panics.
- A Path stays unchanged after failed calls and is shared across goroutines under the race detector; PathString, RootSelectorOnly and the quote flags are checked.
- fail-to-pass tests
- 1,464
- regression tests
- 22,002
- build/type-check gate
- 1
- files in the reference solution
- 30
- lines it adds
- +2,221
Key moments of the trajectory
Calls 3, 7–8 Read the node-based internal/decoder/path.go and each decoder’s DecodePath, and found that evaluation wrote ctx.Option.Path.node, so a shared Path changed mid-call and stayed changed after failures.
Calls 24–27, 34 Confirmed a green test baseline, branched rfc9535-jsonpath-selectors off main, wrote a repro showing Extract returning one value for $..b and PathString dropping the leading $, then recorded an UnmarshalPath benchmark baseline.
Calls 45, 48, 50–51, 54–55, 58, 60 Replaced the node machinery with a new parser, exact-decimal number comparison and one generic evaluator over raw-JSON and Go-value backends, then removed the path-mutating DecodePath from every decoder.
Calls 69, 72, 74, 76 Wrote its own tests, one of which compares Extract, Unmarshal and Get on documents whose keys are already sorted; a printed count then confirmed the check was not vacuous, with 114 of the 164 cases taking part.
Calls 87–88, 90–91, 93–94, 119 A 10,000-deep probe exposed quadratic lookups ($..a ~919 ms); rebuilding path_json.go on a container index made them linear (~3 ms), and later tuning left every probed benchmark faster than main.
Call 99 Mutation-tested the new index scan by breaking its escape carry and its in-string carry; its tests failed on both mutants and passed again once the file was restored.
Calls 102–104, 121, 125–127, 129–130 Ran the full suite, race-detector and GOGC=1 path tests, a 32-bit run, golangci-lint and two fuzzers, all clean, then committed 35 files in one commit and confirmed a clean tree.
What the agent committed
- model calls
- 131
- bash commands
- 146
- minutes
- 120
- files changed
- 35
- lines added / removed
- +4,085 / −1,386
The agent replaced the node-based path code with an RFC 9535 parser and one evaluator shared by raw JSON (Extract, Unmarshal) and Go values (Get), and committed it as one 35-file commit on a new branch.
The agent's committed change (excerpt)
diff --git a/internal/decoder/path_parse.go b/internal/decoder/path_parse.go
new file mode 100644
--- /dev/null
+++ b/internal/decoder/path_parse.go
@@ -0,0 +1,787 @@
…
+// filterOr parses the expression of a filter at pos: the expressions which || joins, which binds the least.
+func (p *pathParser) filterOr() (pathFilter, error) {
+ return p.filterTerms(pathFilterOr, "||", (*pathParser).filterAnd)
+}
+
+// filterAnd parses the expressions which && joins.
+func (p *pathParser) filterAnd() (pathFilter, error) {
+ return p.filterTerms(pathFilterAnd, "&&", (*pathParser).filterBasic)
+}
+
+// filterTerms parses the expressions which the operator joins, each by term: one of them is the expression itself.
+// The white spaces after the last one are skipped.
+func (p *pathParser) filterTerms(kind pathFilterKind, operator string, term func(*pathParser) (pathFilter, error)) (pathFilter, error) {
+ first, err := term(p)
+ if err != nil {
+ return first, err
+ }
+ var terms []pathFilter
+ for {
+ p.skipSpaces()
+ if !strings.HasPrefix(p.text[p.pos:], operator) {
+ break
+ }
+ p.pos += len(operator)
+ p.skipSpaces()
+ next, err := term(p)
+ if err != nil {
+ return next, err
+ }
+ if terms == nil {
+ terms = []pathFilter{first}
+ }
+ terms = append(terms, next)
+ }
+ if terms == nil {
+ return first, nil
+ }
+ return pathFilter{kind: kind, terms: terms}, nil
+}
+
+// filterBasic parses what && and || join: an expression in parentheses or a query, which ! may negate, or a
+// comparison. As in RFC 9535, a comparison is negated in parentheses, and a literal is compared.
+func (p *pathParser) filterBasic() (pathFilter, error) {
+ negated := p.peek() == '!'
+ if negated {
+ p.pos++
+ p.skipSpaces()
+ }
+ var filter pathFilter
+ var err error
+ switch c := p.peek(); {
+ case c == '(':
+ if p.nesting == maxPathNesting {
+ return filter, p.errorf("the parentheses of a filter nest too deep")
+ }
+ p.nesting++
+ p.pos++
+ p.skipSpaces()
+ if filter, err = p.filterOr(); err != nil {
+ return filter, err
+ }
+ if p.peek() != ')' {
+ return filter, p.unexpected("a right parenthesis")
+ }
+ p.pos++
+ p.nesting--
+ case negated && c != '@':
+ return filter, p.unexpected("a left parenthesis or a query after !")
+ default:
+ if filter.left, err = p.operand(); err != nil {
+ return filter, err
+ }
+ var compared bool
+ if !negated {
+ filter.op, compared = p.compareOp()
+ }
+ switch {
+ case compared:
+ filter.kind = pathFilterCompare
+ p.skipSpaces()
+ if filter.right, err = p.operand(); err != nil {
+ return filter, err
+ }
+ case filter.left.query:
+ filter.kind = pathFilterExists
+ default:
+ return filter, p.unexpected("a comparison operator after a literal")
+ }
+ }
+ if negated {
+ filter = pathFilter{kind: pathFilterNot, terms: []pathFilter{filter}}
+ }
+ return filter, nil
+}
…
+// operand parses what a filter compares at pos: a literal or a query.
+func (p *pathParser) operand() (pathOperand, error) {
+ rest := p.text[p.pos:]
+ switch c := p.peek(); {
+ case c == '@':
+ steps, err := p.query()
+ return pathOperand{query: true, steps: steps}, err
+ case c == '\'' || c == '"':
+ str, err := p.quoted()
+ return pathOperand{kind: pathKindString, str: str}, err
+ case c == '-' || ('0' <= c && c <= '9'):
+ return p.number()
+ case strings.HasPrefix(rest, "true"):
+ p.pos += len("true")
+ return pathOperand{kind: pathKindTrue}, nil
+ case strings.HasPrefix(rest, "false"):
+ p.pos += len("false")
+ return pathOperand{kind: pathKindFalse}, nil
+ case strings.HasPrefix(rest, "null"):
+ p.pos += len("null")
+ return pathOperand{kind: pathKindNull}, nil
+ case c == '$':
+ return pathOperand{}, p.errorf("a query from the root identifier $ is not supported in a filter")
+ }
+ // the name of a function of RFC 9535, which its arguments follow in parentheses
+ name := strings.TrimLeft(rest, "_abcdefghijklmnopqrstuvwxyz0123456789")
+ if len(name) < len(rest) && strings.HasPrefix(name, "(") {
+ return pathOperand{}, p.errorf("the function %s is not supported in a filter", rest[:len(rest)-len(name)])
+ }
+ return pathOperand{}, p.unexpected("a literal or a query")
+}
… 5,878 more lines not shown (35 files changed in all)
Customization
Including, but not limited to, the following; anything else is open for discussion.
- Languages and codebases: languages and stacks (Java, C++ and others too), kinds and sizes of codebases, size of the change.
- Task mix and requests: new features, enhancements, behaviour changes, fixes, refactors; length and tone of the request, how much of the interface it names.
- Difficulty: the reference model and the pass-rate band to hit.
- Verification: number of hidden tests, regression scope, build and type-check gates.
- Trajectories: agent harness (the DeepSWE leaderboard’s bash-only harness, Claude Code and others), model and reasoning effort, trajectories per task.
- Format, volume and schedule: Harbor or Pier task directories, ATIF or request-level trajectories; how many and by when.